Подтвердить что ты не робот

Как ссылка printf() var-arg взаимодействует с макетом памяти стека?

С учетом фрагмента кода:

int main()
{
    printf("Val: %d", 5);
    return 0;
}

Есть ли какая-либо гарантия, что компилятор будет хранить "Val: %d" и '5' смежно? Например:

+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| ... |  %d | ' ' | ':' | 'l' | 'a' | 'V' | '5' | ... |
+-----+-----+-----+-----+-----+-----+-----+-----+-----+
      ^                                   ^     ^
      |           Format String           | int |

Как именно распределяются эти параметры в памяти?

Кроме того, выполняет ли функция printf значение int относительно строки формата или по абсолютной величине? Так, например, в данных

+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| ... |  %d | ' ' | ':' | 'l' | 'a' | 'V' | '5' | ... |
+-----+-----+-----+-----+-----+-----+-----+-----+-----+
      ^                                   ^     ^
      |           Format String           | int |

когда функция встречает %d, уже будет сохраненный адрес памяти для первого параметра функции, на который будет ссылаться, или будет вычисляться значение относительно первого элемента строки формата?

Извините, если я запутался, моя основная цель - понять, как использовать форматирование строк, где пользователю разрешено предоставлять строку формата, как описано в этом документе.

http://www.cis.syr.edu/~wedu/Teaching/cis643/LectureNotes_New/Format_String.pdf

Мои проблемы возникают при атаке, описанной на стр. 3 и 4. Я решил, что %x должен пропустить 16 бит, которые занимает строка, что указывает на то, что функция, выделенная смежно, и ссылки относительно, но другие источники указывают, что не гарантируется, что компилятор должен распределять смежно, и я был обеспокоен тем, что документ был упрощенным.

4b9b3361

Ответ 1

есть ли какая-либо гарантия, что компилятор будет хранить "Val:% d" и "5" смежно

Это практически гарантировано, что их не будет. 5 достаточно мал, чтобы его можно было встроить прямо в поток команд, а не загружать через адрес памяти (указатель) - что-то вроде movl #5, %eax и/или сопровождаться нажатием на стек, тогда как строковый объект будет выложенная в области данных только для чтения исполняемого изображения, и будет указана с помощью указателя. Мы говорим о макете времени компиляции исполняемого образа.

Если вы не имеете в виду макет времени исполнения стека, в котором да, указатель размера слова для этой строки и константа 5 размера слова будут рядом друг с другом. Но порядок, вероятно, обратный тому, что вы ожидаете - исследование "вызов вызова функции C".

[Редактирование позже: запуск некоторых примеров кода с -S (сборка) теперь; Мне напомнили, что при использовании световых регистров в вызывающем устройстве (т.е. Регистры процессора могут быть перезаписаны без вреда) и несколько аргументов вызываемой функции, аргументы могут передаваться полностью через регистры для сохранения инструкций и памяти. Поэтому макет стека на самом деле сложно предсказать, даже если злоумышленник имел доступ к исходному коду. Особенно с gcc -O2, который разрушил мою основную → my_function → последовательность функций printf в main → printf]

В большинстве эксплойтов используется переполнение стека, поскольку вредоносный код запускается в кирпичную стену, пытаясь изменить память в вышеупомянутой области данных только для чтения - ОС прерывает процесс.

Поведение printf отличается тем, что строка формата похожа на миниатюрную компьютерную программу, которая сообщает printf рассматривать аргументы в стеке для каждого спецификатора формата%, который он находит. Если эти аргументы никогда не были фактически вытолкнуты и/или имели разные размеры, printf будет слепо пересекать части стека, которые он не должен, и, возможно, показывать данные дальше по стеку (вниз по цепочке вызовов), где могут находиться личные данные. Если первый аргумент printf является по крайней мере константой, компилятор может по крайней мере предупредить вас, когда последующие аргументы не соответствуют спецификаторам "%", но когда это переменная, все ставки отключены.

printf ужасно с точки зрения безопасности и является вычислительно интенсивным, но очень мощным и выразительным. Добро пожаловать на C.: -)

2-е позже редактировать Теперь ваш первый вопрос в комментариях... поскольку вы можете видеть свою терминологию и, возможно, мысли были немного искажены. Изучите следующее, чтобы понять, что происходит. Не беспокойтесь о указателях на строки. Это было скомпилировано с gcc 4.8.2 на Linux 3.13 64-bit без флагов. Обратите внимание на то, как чрезмерное использование спецификаторов формата по существу проходит назад через стек, раскрывая аргументы, которые были переданы в предыдущем вызове функции.

/* Do not compile this at home. */
#include <stdio.h>

int second() {
  printf("%08X %08X %08X %08X %08X %08X %08X %08X\n");
}

int first(int a, int b, int c, int d, int e, int f, int g, int h) {
  second();
}

int main(int argc, char **argv) {
  first(0xDEEDC0DE, 0x1EADBEEF, 0x11BEDEAD, 0xCAFAF000, 0xDAFEBABE, 0xAACEBACE, 0xE1ED1EAA, 0x10F00FAA);
  return 0;
}

Два обратных цикла, вывод stdio:

1EADBEEF 11BEDEAD CAFAF000 DAFEBABE AACEBACE 75F83520 00400568 88B151C8

1EADBEEF 11BEDEAD CAFAF000 DAFEBABE AACEBACE 8B4CBDC0 00400568 7BB841C8

Ответ 2

Интересный вопрос. Вот результат сборки из двух тестовых программ: один 32-разрядный/MSVC, другой 64-разрядный GCC:

Программа тестирования:

/*
 * Sample output:
 * A
 * B: 49, 2, 5.000000
 */
#include <stdio.h>

int main(int argc, char *argv[]) {
  printf ("A\n");
  printf ("B: %d, %c, %f\n", 0x31, 0x32, 5.0);
  return 0;
}

MSVS/32-разрядная сборка (cl /Fa):

_DATA   SEGMENT
$SG2938 DB  'A', 0aH, 00H
    ORG $+1
$SG2939 DB  'B: %d, %c, %f', 0aH, 00H
...
CONST   SEGMENT
[email protected] DQ 04014000000000000r   ; 5
...
    push    OFFSET $SG2938
    call    _printf
...
    movsd   xmm0, QWORD PTR [email protected]
    movsd   QWORD PTR [esp], xmm0
    push    50                  ; 00000032H
    push    49                  ; 00000031H
    push    OFFSET $SG2939
    call    _printf

GCC/64-разрядная сборка (gcc -S):

.LC0:
        .string "A"
.LC1:
        .string "B: %d, %c, %f\n"
...
        movl    %edi, -4(%rbp)   // You'll notice that GCC substitutes "puts()" for "printf()" here
        movq    %rsi, -16(%rbp)
        movl    $.LC0, %edi
        call    puts
...
        movl    $.LC1, %eax     // Also notice the absence of "push": we're passing arguments in registers, instead of on the stack
        movsd   .LC2(%rip), %xmm0
        movl    $50, %edx
        movl    $49, %esi
        movq    %rax, %rdi
        movl    $1, %eax
        call    printf