Все строки, читаемые как с клавиатуры, так и из файла паролей, гарантированно влезают в отведенный им буфер и ни при каких обстоятельствах не могут выйти за его границы. При условии, что у злоумышленника нет доступа к файлу “buff.psw”, содержащего пароли пользователей [313], он никак не сможет обойти защиту [314]. Кажется, в десятке строк трудно ошибиться, и никаких дыр тут нет.
Психологическая инерция подводит и на этот раз. И, видимо, не только разработчиков, но, в том числе, и злоумышленников, поскольку тип атаки, описанный ниже, не получил большого распространения. Поэтому, многие из приложений, считающиеся защищенными, все же содержат грубые ошибки, позволяющие легко и незаметно проникнуть в систему.
Речь идет о «большой дыре» в функции “printf”, вернее дыра находится не в одной конкретной функции (тогда бы она могла бы быть безболезненно устранена), а в самом языке Си. Одни из его недостатков заключается в том, что функция не может определить сколько ей было передано параметров. Поэтому, функциям с переменным количеством аргументов, приходится каким-то образом передавать и число этих самых аргументов.
Функция “printf” использует для этой цели строку спецификаторов, и ее вызов может выглядеть, например, так: “printf(“Name: %s\nAge: %d\nIndex: %x\n”, amp;s[0],age,index)”. Количество спецификаторов должно быть равно количеству передаваемых функции переменных. Но что произойдет, если равновесие нарушится?
Возможно два варианта - переменных больше, чем спецификаторов и переменных меньше, чем спецификаторов. Пока количество спецификаторов не превышает количества переданных параметров, не происходит ничего интересного, поскольку, из стека аргументы удаляются не самой функцией, а вызывающим ее кодом (который уж наверняка знает, сколько аргументов было передано) разбалансировки стека не происходит и все работает нормально. Но если количество спецификаторов превышает количество требуемых аргументов, функция, пытаясь прочитать очередной аргумент, обратится к «чужим» данным! Конкретное поведение кода зависит от компилятора и содержимого стека на момент вызова функции “printf”.
Сказанное будет рассмотрено ниже на примере следующей программы (на диске, прилагаемом к книге, она находится в файле “/SRC/printf.bug”):
Если ее откомпилировать с помощью Microsoft Visual Studio 5.0-6.0, результат работы окажется следующий:
Программа выдала два числа, несмотря на то, что ей передавали всего одну переменную ‘a’. Каким же образом она сумела получить значение ‘b’? (а в том, что ‘777’ это действительно значение переменной ‘b’ сомневаться не приходится). Ответить на этот вопрос помогает дизассемблирование:
·.text:00401000 main proc near.text:00401000
·.text:00401000 var_8 = dword ptr -8
·.text:00401000 var_4 = dword ptr -4
·.text:00401000
·.text:00401000 push ebp
·.text:00401001 mov ebp, esp
·.text:00401001;
·.text:00401003 sub esp, 8
·.text:00401003;
·.text:00401006 mov [ebp+var_4], 666h
·.text:00401006
·.text:0040100D mov [ebp+var_8], 777h
·.text:0040100D
·.text:00401014 mov eax, [ebp+var_4]
·.text:00401014
·.text:00401017 push eax
·.text:00401017
·.text:00401018 push offset aXX; "%x %x\n"
·.text:00401018;