printf () влияет на ситуацию переполнения буфера - PullRequest
0 голосов
/ 23 мая 2018

У меня есть простая программа, которая инициализирует строку стиля ac, а затем инициализирует символ.Затем я использую функцию 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 влияет на ситуацию переполнения буфера?

edit: добавлено в сборку для двух ситуаций.сгенерированная сборка для первого случая (без 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 голосов
/ 23 мая 2018

Сначала давайте посмотрим, почему переполнение буфера не произошло во втором примере.

Просмотр вашего вывода:

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

Мы можем видеть, что str на выше x в стеке.

Строка "Hello world" принимает адреса памяти от 0061FF2A до 0061FF36

Стек выглядит как-токак

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), поскольку мы видим десятичное значение ASCII для размещения 'X'

Это не слишком большой прыжок, чтобы предположить, что 5-символьная строка "Testt" хранится в 25(%esp) какРасстояние между 25(%esp) и 31(%esp) достаточно для хранения 5 символов и нулевого терминатора.

Итак, мы знаем, что str в 25(%esp) и x в 31(%esp).Стек должен выглядеть примерно так:

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

Теперь мы можем легко увидеть, что str предшествует x, и ясно, почему запись после конца str приведет к xпереписать.


Теперь главный вопрос, Почему это работает в первом случае, а не во втором?

По какой-то причине компилятор решилпоместите x после str в первом примере и x перед str во втором примере.

Как отмечалось в комментариях, точное расположение локальных переменных в стеке не являетсяопределяется C. Компилятор может решить порядок, в котором он хочет, чтобы вещи хранились, и может изменить этот порядок от программы к программе по неочевидным причинам.

По сути, точное расположение и порядок локальных переменных в стекенеопределенное, и поэтому неопределенное поведение - вот почему переполнение буфера работает в одном случае, но не в другом.

...