На главную | Содержание | Назад | Вперёд
Наши друзья

 

 

Арифметические ошибки при управлении памятью

Ошибки при выполнении арифметических операций, особенно при операциях с указателями, могут привести к неверному вычислению размера буфера, а следовательно, и к переполнению буфера. Во время создания этой книги ошибки при выполнении арифметических операций с указателем оставались сравнительно неисследованной для хакеров областью. Однако в очень опасных атаках на переполнение буфера с последующим получением привилегий суперпользователя, использовались именно эти ошибки.
Значения размера буфера часто могут быть заданы хакером как непосредственно, так и обходным путем. Непосредственно эти значения часто можно задавать с помощью заголовков пакетов (которые способен подменять хакер). Обходным путем можно назвать использование функции strlen () по отношению к контролируемому пользователем буферу. В последнем случае хакер получает контроль над вычислениями длины числа, управляя размером внедряемой строки.
Отрицательные числа как большие положительные числа
В современных компьютерах числа отображаются различными интересными способами. Иногда целые числа могут оказаться настолько большими, что они "переполняют" целочисленное представление этого числа, используемое компьютером. При вводе тщательно подготовленной строки злоумышленник способен получить в результате вычисления длины числа отрицательное значение. В результате загадочных преобразований,*отрицательное значение обрабатывается как беззнаковое число, а точнее, как очень большое положительное число. Рассмотрим только один простейший пример такого преобразования, когда число -1 (для 32-битовых целых чисел) отображается как OxFFFFFFFF, что расценивается как большое беззнаковое число 4294967295.
Теперь рассмотрим следующий фрагмент кода.
int main(int argc, char* argv[]) {
char _t[10] ;
char p[]="xxxxxxx"; char k[]="zzzz";
strncpy(_t, p, sizeof (__t) ) ;
strncat(_t, k, sizeof(_t) - strlen(_t) - 1); return 0;
}
После исполнения результирующая строка в параметре -t будет иметь значение xxxxxxxzz.
Если мы предоставим точно 10 символов для параметра р (хххххххххх), то значения функций sizeof (_t) и strlen (_t) будут одинаковыми и окончательный результат вычислений составит -1 или OxFFFFFFFF. Поскольку аргумент, передаваемый функции strncat (), должен быть беззнаковым, все заканчивается тем, что

число интерпретируется как большое положительное число, а размер значения функции никак не ограничивается. В результате происходит искажение данных в стеке, что обеспечивает хакеру возможность перезаписи указателя команд или других значений, сохраненных в стеке.
Искаженный стек выглядит примерно следующим образом.
0012FF74 78 78 78 78 хххх
0012FF78 78 78 78 78 хххх
0012FF7C 78 78 СС СС xxii
0012FF80 СО FF 12 7А Ay. z <- искажение происходит здесь
0012FF84 7А 7А 7А 00 zzz. <- и здесь.
Обнаружение проблемы в коде
0040D603 call
0040D608 add
0040D60B mov
0040D610 sub
0040D612 sub
strlen (00403600) esp, 4 ecx,OAh ecx,eax
ecx,1 <- подозрительное место
В предьщущем фрагменте кода мы видим вызов функции strlen и несколько операций вычитания. Это удачное место для проверки возможной проблемы с длиной знакового числа.
Для 32-битовых знаковых чисел максимальным значением является 0x7FFFFFFF, а минимальным — 0x80000000. Хитрость заключается в том, чтобы заставить выполнить преобразование из положительного числа в отрицательное и наоборот (иногда с минимальными изменениями).
Опытные хакеры для таких преобразований выбирают числа в районе минимального или максимального значения, как показано на 8.
Несоответствие между знаковыми и беззнаковыми значениями
Большинство арифметических ошибок возникает из-за различий между знаковыми и беззнаковыми значениями. Довольно часто в программах выполняются действия сравнения, в результате чего исполняется блок кода, если число меньше заданного значения. Например:
if (X < 10) {
do_something(X);
}
Если значение переменной X меньше 10, то исполняется блок кода (dosomething). Значение переменной X затем передается функции do something (). Теперь рассмотрим ситуацию, когда значение X равно -1. Безусловно, это значение меньше 1, поэтому должен быть исполнен блок кода. Однако не забывайте, что -1 — это то же самое, что и OxFFFFFFFF. Если функция обрабатывает X как беззнаковую перемен­ную, то X обрабатывается как очень большее число, конкретно как 4294967295.
В реальности эта ситуация может возникнуть, когда значение X получается на основе предоставленного хакером числа или, исходя из длины строки, которая передается программе. Рассмотрим следующий фрагмент кода.
void parse(char *р) <
int size ■ *р;
char _test(12);
int sz ■ sizeof( test) ;
if( size < sz )
<
memcpy(test, p, size);
}
)
int main(int argc, char* argv [ ])
i
// какой-то пакет
char _t(] = "\x05\xFF\xFF\xFF\xlO\xlO\xlO\xlO\xlO\xlO"; char *p - _t; parse (p);
return 0;
}
Код анализатора получает сведения о размере переменной из *р. Для примера предоставляем значение 0xFFFFFF05 (в прямом порядке следования байтов). Если это число со знаком, то оно соответствует -251 в десятичной системе счисления. Если это беззнаковое значение, тогда оно соответствует 4294967045 — очень большое число. Понятно, что -251 значительно меньше, чем размер выделенного буфера. Од­нако поскольку функция memcpy не работает с отрицательными значениями, то данное число обрабатывается как большое беззнаковое значение. В приведенном выше коде использование функции memcpy для значения размера беззнакового int приводит к возникновению обширного переполнения буфера.
Выявление проблемы в коде
Обнаружить ошибки, связанные с использованием знака в коде, полученном с помощью дизассемблера, достаточно просто, поскольку вполне возможно увидеть

Мы видим, что та же область памяти сравнивается (и выполняется операция перехода) с помощью команды j le — сравнение чисел со знаком. Это должно вызвать у нас подозрения, поскольку переход в одной и той же области памяти выполняется по одинаковому критерию как для беззнакового числа, так и для числа со знаком. Хакеры любят подобные ситуации.
Исследование проблемы с помощью программы IDA
Можно также выполнять поиск потенциальных ошибок несоответствия чисел с помощью исследования дизассемблированного кода.
Для операции сравнения беззнаковых чисел следует искать такие команды:
JA
JB
JAE
JBE
JNB
JNA

Для операции сравнения чисел со знаком:
JG JL JGE JLE
Можно воспользоваться дизассемблером наподобие IDA для выявления всех операций с переменными со знаком. Это позволяет получить список интересных областей кода, как показано на 9.
Вместо последовательной проверки всех операций, можно выполнить поиск регулярных выражений, которые используются во всех вызовах. На 10 показан результат использования в качестве строки поиска j [gl ].
Даже в небольших фрагментах программ можно легко найти области кода, в которых используются значения со знаком. Если эти области находятся поблизости точек, возле которых обрабатываются пользовательские данные (т.е. присутствует вызов функции recv () ), то дальнейшее исследование может подтвердить, что данные были использованы в операции с числами со знаком. Очень часто такая ситуа­ция может использоваться для организации логических и арифметических ошибок.
Значения со знаком и управление памятью
Подобные ошибки часто можно найти в процедурах управления памятью. Типичная ошибка в программном коде может выглядеть следующим образом.
int user_len;
int target_len = sizeof (__t) ; user_len = 6;
if (targetlen > user len) {
memcpy(_t, u, a) ;
}
Значения int указывают на выполнение операций сравнения с числами со знаком, в то время как функция memcpy использует беззнаковые значения. При компиляции этой ошибки не выдается никакого предупреждения. Если значение функции контролируется хакером, то предоставление большого числа, например 0х8000000С приведет к тому, что функция будет обрабатывать очень большое число.
Мы можем обнаруживать переменные, которые учитывают размер числа в дизас-семблированном коде, как показано на 11. В данном случае мы видим sub edi, eax

где edi используется как беззнаковая переменная. Если хакер сможет управлять либо значением edi, либо значением еах, то он постарается изменить edi, заставить это значение перейти нулевую границу и оказаться равным -1.
Подобным образом мы можем выполнить поиск ошибок, связанных с арифметическими действиями с указателями (12).
Поиск по выражению е . х. е . х предоставляет длинный список областей кода (13). Если одно из значений, показанных на 13, контролируется пользователем, то искажение памяти становится вполне решаемой задачей.

 

На главную | Содержание | Назад | Вперёд
 
Яндекс.Метрика