Почему расположение строки формата в стеке так отличается в разных системах Linux?

#c

#c

Вопрос:

Когда вы вызываете функцию, подобную printf, formatstring и аргументы помещаются в стек. Если вы опускаете параметры, но указываете их в строке формата с помощью «%x» или «%s» или «%n», вы не можете получить доступ (прочитать или записать) к formatstring. В одной системе, которую я тестировал, строка формата была 4-м аргументом. В другом случае это было за пределами 200-го.

Например, я получил следующую программу, которая уязвима для эксплойта formatstring и содержит следующее утверждение:

 printf(userSuppliedString);
 

Нет, я хочу прочитать конкретный адрес. Например. 0xbffffdd7.
Я называю это следующим образом:

 ./fmt_vuln $(printf "xd7xfdxffxbf")%08x.%08x.%08x.%s
 

В этом примере строка формата является четвертым параметром («%s»). Таким образом, %s будет принимать начало строки формата. Поскольку это адрес, который мы указали, будет напечатано содержимое этого адреса.

Теперь на этой машине formatstring является четвертым параметром. Но в других системах Linux это что-то совершенно другое. Почему это так?

Комментарии:

1. Можете ли вы привести какой-нибудь пример кода, чтобы проиллюстрировать свой вопрос?

2. Вы говорите о расположении содержимого строки? Все, что передается в стек, — это указатель на строку (т. Е. Адрес, По которому она выделена). Для функций varargs параметры обычно перемещаются справа налево, поэтому они будут вверху стека (поэтому printf всегда можно найти первый аргумент).

Ответ №1:

Когда вы вызываете функцию, подобную printf, formatstring и аргументы помещаются в стек. Если вы опускаете параметры, но указываете их в строке формата с помощью «%x» или «%s» или «%n», вы не можете получить доступ (прочитать или записать) к formatstring. В одной системе, которую я тестировал, строка формата была 4-м аргументом. В другом случае это было за пределами 200-го.

Нет, возможно, вы неправильно поняли.

Когда вы вызываете printf с одним аргументом — строкой формата, указатель на строку формата помещается в стек. char * Это указатель This может указывать на любое место в памяти — printf просто выполняет запрос и считывает эту ячейку памяти как строку формата.

В обычном случае с одним аргументом вы передаете строковый литерал printf ("hello world!"); компилятору, который помещает текст hello world куда-то в память и генерирует указатель на него для передачи в printf. Затем он выполняет все, что, согласно соглашениям о вызовах, он должен делать для вызова функции — например, на x86 он перемещает указатель на стек. Затем Printf считывает свой первый аргумент из стека и доволен!

В обычном случае с аргументом n то же самое происходит со строковым литералом и указателем. Для вызова функции компилятор передает каждое из значений. Снова с использованием x86 (потому что нажатие легче описать, чем, скажем, ARM, у которого сложная схема передачи аргументов) Эти значения помещаются в стек справа налево. Итак, если у вас есть вызов printf ("%d, %s, %d", x, name, y); y, который передается в стек, затем name , x и, наконец, строка формата.

Теперь внутри printf мы читаем наш первый аргумент (получаем его из стека). Это char * указание на "%d, %s, %d" . Мы можем прочитать это, а затем — зная, как компилятор передает аргументы, мы можем прочитать три вещи, которые были помещены в стек — снова мы счастливы!

Уязвимость строки формата работает за счет несоответствия убеждений, которые имеет printf, и убеждений, которые имеет компилятор.

Мы можем показать это, вызвав неопределенное поведение, вызванное передачей неправильного количества аргументов в printf . при вызове printf ("%s"); компилятор не вводит аргумент, который соответствовал бы char * printf, который ожидает использовать для выполнения %s директивы. Но — поскольку printf не знает, что компилятор этого не делал, он все равно ищет аргумент в стеке. Он извлекает неопределенное значение из стека и пытается прочитать строку, на которую оно указывает.

В вашем случае вы разрешаете передавать строки произвольного формата в printf. Они, безусловно, имеют несоответствие между количеством ожидаемых аргументов и количеством переданных аргументов, и поэтому printf считывает стек, который заполнен мусором.

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

Итак, имея в виду это описание, я не могу найти способ проанализировать ваш вопрос, который имеет смысл. Возможно, вы можете быть более ясными, и я могу обновить свой ответ?

Ответ №2:

 ./fmt_vuln $(printf "xd7xfdxffxbf")%08x.%08x.%08x.%s
 

В этом примере строка формата является четвертым параметром («%s»).

Нет, не совсем. Проблема в том, что вы не получаете доступ к четвертому параметру printf , вместо этого вы получаете доступ к локальной переменной или параметру в его вызывающей функции (или дальше по стеку). Следовательно, это полностью зависит от кода вызывающей функции. Для демонстрации, что он делает на 386:

 Breakpoint 1, __printf (format=0xbffff543 "%p") at printf.c:29
29      printf.c: Adresář nebo soubor neexistuje.
        in printf.c
(gdb) x/120a $ebp

Description:    $esp            return addr             fmtstring       parameters

0xbffff2d8:     0xbffff2f8      0x80483fd <main 25>     0xbffff543      0xb7ff1310
0xbffff2e8:     0x804842b <__libc_csu_init 11>  0xb7fb7ff4      0x8048420 <__libc_csu_init>     0x0
0xbffff2f8:     0xbffff378      0xb7e78e46 <__libc_start_main 230>      0x2     0xbffff3a4
0xbffff308:     0xbffff3b0      0xb7fe1860      0xb7ff7411      0xffffffff
0xbffff318:     0xb7ffeff4      0x8048254       0x1     0xbffff360
0xbffff328:     0xb7ff0996      0xb7fffac0      0xb7fe1b58      0xb7fb7ff4
0xbffff338:     0x0     0x0     0xbffff378      0xa32ae5c4
0xbffff348:     0x93d0f3d4      0x0     0x0     0x0
0xbffff358:     0x2     0x8048330 <_start>      0x0     0xb7ff65b0
0xbffff368:     0xb7e78d6b <__libc_start_main 11>       0xb7ffeff4      0x2     0x8048330 <_start>
0xbffff378:     0x0     0x8048351 <_start 33>   0x80483e4 <main>        0x2
0xbffff388:     0xbffff3a4      0x8048420 <__libc_csu_init>     0x8048410 <__libc_csu_fini>     0xb7ff1310
0xbffff398:     0xbffff39c      0xb7fff908      0x2     0xbffff539
0xbffff3a8:     0xbffff543      0x0     0xbffff546      0xbffff55a
0xbffff3b8:     0xbffff56a      0xbffff581      0xbffff58c      0xbffff5dc
0xbffff3c8:     0xbffff5f3      0xbffff654      0xbffff66f      0xbffff689
 

Как вы можете видеть, строка формата присутствует только дальше в памяти, в области, инициализированной средой выполнения libc, где argv указывает на . Вам нужно лучше изучить код, который вы атакуете.