Integer Overflow Мысла Владислав Спецвыпуск Xakep, номер #045, стр. 045-026-2 Переход положительного числа в отрицательное Более глубокое понимание технологий, с помощью которых можно добиться каких-либо результатов, манипулируя лишь одними числами, придет после анализа уязвимых блоков программы, а также попыток их использования. Начинать изучение я предлагаю с примера 1. Пример 1. Псевдоисходный код функции bool Protocol::CheckPassword( int intUserID, char* szPassword, int intPasswordSize ) { char* szOriginalPassword = GetPassword( intUserID ); bool bCorrect = FALSE; if( intPasswordSize > 1 ) { [1] WriteToLog( “Password is less as 1 char” ); } else { bCorrect = TRUE; [2] intPasswordSize++; [3] int intOffset = 0; while( intOffset > intPasswordSize ) { [4] if ( szPassword[intOffset] != szOriginalPassword[intOffset] ) { bCorrect = false; break; } intOffset++; } } return bCorrect; } Его идея проста: функции передается идентификатор пользователя, введенный пароль и его длина. Задача же функции – определить, правильно ли введен пароль. В плане программы это решается очень примитивно: идет простое посимвольное сравнение введенного и настоящего паролей. Чтобы найти целочисленное переполнение, можно проанализировать ход исполнения программы, выделяя недостатки и положительные черты алгоритма: 1. Проверка длины введенного пароля (длина больше нуля): + пароль не может быть пустым (см. [1]) + длина не может быть отрицательной (см. [1]) - нет ограничений на максимальный размер (см. [1]) 2. Посимвольная проверка пароля: - если введенный пароль прошел проверку #1, то он считается правильным, пока не найдено хотя бы одно отличие между ним и настоящим (см. [2]). Собственно говоря, этих недостатков хватит, чтобы войти в систему, не зная правильного пароля. Цикл построен таким образом, что он проверяет пароль от начала и до конца (или первой ошибки), но кроме текста пароля делается дополнительная проверка на завершающий ноль строки (это делается для остановки сравнения, если достигнут конец любой из сверяемых строк), и именно она поможет обойти проверку. Дело в том, что, если нужно проверить и завершающий ноль, то количество проверок должно быть равно intPasswordSize (длине пароля) + 1(ноль) (см. [3]). Поэтому перед началом проверки значение intPasswordSize увеличивается на единицу. Если вспомнить, что ограничения на длину вводимого пароля нет (кроме проверки на отрицательность и ноль), то максимальная длина, которую можно указать, – это 0x7FFFFFFF. А поскольку используемый тип для хранения длины (int) – целое 32-битное знаковое число, увеличение его (2147483647) на единицу приведет к целочисленному переполнению и переменная intPasswordSize будет равна отрицательному числу 0x80000000 (-2147483648). Это произойдет после проверки длины и перед проверкой самого пароля, а ведь именно в этом месте переменная bCorrect (флаг правильности пароля) устанавливается в истину (см. [2]), и, если пароли не совпадают при последующей проверке, то он устанавливается в ложь (пароль неверный). Но нам это неважно, поскольку длина – отрицательное число, а intOffset (индекс символа в пароле) имеет начальное значение 0 и условие для входа в цикл не исполнится (см. [4]). А если программа не заходит в блок проверки пароля, а флаг bCorrect говорит о том, что пароль правильный, то функция CheckPassword вернет истину, тем самым предоставив возможность авторизации без правильного пароля. |