·.text:00401018;
·.text:00401018; +8 off aXX (‘%x %x’) (строка спецификаторов)
·.text:00401018; +4 var_4 (‘a’) (аргумент функции printf)
·.text:00401018; 0 var_8 (‘b’) (локальная переменная)
·.text:00401018; -4 var_4 (‘a’) (локальная переменная)
·.text:0040101D call printf
·.text:0040101D;
·.text:00401022 add esp, 8
·.text:00401022;
·.text:00401025 mov esp, ebp
·.text:00401025;
·.text:00401027 pop ebp
·.text:00401028 retn
·.text:00401028 main endp
Итак, содержимое стека на момент вызова функции printf такого (смотри комментарии к дизассемблированному листингу) [315]:
· +8 off aXX (‘%x %x’) (строка спецификаторов)
· +4 var_4 (‘a’) (аргумент функции printf)
· 0 var_8 (‘b’) (локальная переменная)
· -4 var_4 (‘a’) (локальная переменная)
Но функция не знает, что ей передали всего один аргумент, - ведь строка спецификаторов требует вывести два (“%x %x). А поскольку аргументы в Си заносятся слева на право, самый левый аргумент расположен в стеке по наибольшему адресу. Спецификатор “%x” предписывает вывести машинное слово [316], переданное в стек по значению. Для сравнения - вот как выглядит стек на момент вызова функции “printf” в следующей программе (на диске, прилагаемом к книге, она расположена в файле “/SRC/printf.demo.c”):
· +12 off aXX (‘%x %x’) (строка спецификаторов)
· +08 var_4 (‘a’) (аргумент функции printf)
· +04 var_8 (‘b’) (аргумент функции printf)
· 00 var_8 (‘b’) (локальная переменная)
· -04 var_4 (‘a’) (локальная переменная)
Дизассемблированный листинг в книге не приводится, поскольку он практически ни чем не отличается от предыдущего (на диске, прилагаемом к книге, он расположен в файле “/SRC/printf.demo.lst”). В стеке по относительному смещению [317] +4 расположен второй аргумент функции. Если же его не передать, то функция примет за аргумент любое значение, расположенное в этой ячейке.
Поэтому, несмотря на то, что функции была передана всего лишь одна переменная, она все равно ведет себя так, как будто бы ей передали полный набор аргументов (а что ей еще остается делать?):
· +8 off aXX (‘%x %x’) (строка спецификаторов)
· +4 var_4 (‘a’) (аргумент функции printf)
· 0 var_8 (‘b’) (локальная переменная)
· -4 var_4 (‘a’) (локальная переменная)
Разумеется, в нужном месте стека переменная ‘b’ оказалась по чистой случайности. Но в любом случае - там были бы какие-то данные. Определенным количеством спецификаторов можно просмотреть весь стек - от верхушки до самого низа! Весьма велика вероятность того, что в нем окажется данные, интересные злоумышленнику. Например, пароли на вход в систему.
Теперь становится понятной ошибка, допущенная разработчиком buff.printf.c. Ниже приведен дизассемблированный листинг с подробными пояснениями (на диске, прилагаемом к книге, он находится в файле “/SRC/demo.printf.lst”):
·.text:00401000; --------------- S U B R O U T I N E ---------------------------------------
·.text:00401000
·.text:00401000; Attributes: bp-based frame
·.text:00401000
·.text:00401000 main proc near; CODE XREF: start+AFp
·.text:00401000
·.text:00401000 var_54 = byte ptr -54h
·.text:00401000 var_44 = byte ptr -44h
·.text:00401000 var_34 = byte ptr -34h
·.text:00401000 var_14 = dword ptr -14h
·.text:00401000 var_10 = byte ptr -10h
·.text:00401000
·.text:00401000 push ebp
·.text:00401001 mov ebp, esp
·.text:00401001
·.text:00401003 sub esp, 54h
·.text:00401003
·.text:00401006 push offset aPrintfBugDemo; "printf bug demo\n"
·.text:00401006
·.text:0040100B call _printf
·.text:0040100B
·.text:00401010 add esp, 4
·.text:00401010
·.text:00401013 push offset aR; "r"
·.text:00401013
·.text:00401018 push offset aBuff_psw; "buff.psw"
·.text:00401018;
·.text:0040101D call _fopen
·.text:0040101D;
·.text:00401022 add esp, 8
·.text:00401022;
·.text:00401025 mov [ebp+var_14], eax