Издательский дом ООО "Гейм Лэнд"ЖУРНАЛ ХАКЕР #83, НОЯБРЬ 2005 г.

Суровый байт-одиночка

1dt.w0lf

Хакер, номер #083, стр. 083-064-1


Однобайтовое переполнение буфера для взлома программ и сетевых демонов

Если ты следишь за багтрак-лентами, то наверняка сталкивался с упоминаниями об «однобайтовом переполнении буфера» в различных сетевых сервисах. На первый взгляд, может показаться, что это что-то несущественное: ну подумаешь, удастся перезаписать один байт памяти, ерунда какая. Однако интуитивное предположение, что ничего ценного из этого извлечь невозможно, - неверное. Сейчас я расскажу тебе, каким образом возможно наиболее эффективно использовать эту лазейку.

[начало]

Прежде всего следует разобраться с тем, откуда возникает такая проблема. В некоторых строковых функциях завершающий символ всегда размещается в конце строки и, при недостаточном знании программистом особенностей таких функций, может привести к размещению данного символа за концом буфера, выделенного для хранения строки. В этом случае происходит ситуация, при которой перезаписывается один байт памяти. Эта, на первый взгляд, незначительная проблема может привести к полному захвату контроля над исполняемой программой. Чтобы не быть голословным, рассмотрю конкретный пример. Предположим, что какой-то программист написал следующий код (только не спрашивай меня, для чего он это сделал :)):

Пример уязвимого кода

#include <stdio.h>

void cat4(char *);

int main(int argc, char **argv)

{

char a[100] = "";

strncpy(a,argv[1],99);

cat4(argv[2]);

}

void cat4(char * str)

{

char b[4] = "";

strncat(b,str,4);

}

На первый взгляд, все нормально и никаких ошибок в коде нет. Однако опытный человек сразу заметит: программист забыл о том, что функция strncat всегда добавляет символ NULL в конец, строки и, таким образом, при передаче этой функции строки из 4 символов, реально записано в память будет 5. То есть 4 байта строки + NULL. Этот символ NULL будет записан вне области памяти, выделенной для хранения строки b, и, соответственно, перезапишет находящиеся там данные. Чтобы определить, что именно будет перезаписываться, необходимо разобраться с тем, что происходит при вызове функции.

[что происходит?]

При вызове cat4 из main выполняется команда ассемблера call, при этом в стек проталкивается адрес возврата, это значение будет адресом команды, следующей за текущим eip. Далее за записью адреса возврата следует то, что называется прологом процедуры. На этом этапе в стек проталкивается текущее значение EBP, оно называется сохраненным указателем кадра и позднее применяется для восстановления исходного состояния EBP при выходе из функции. Далее текущее значение ESP копируется в EBP и устанавливает новый кадр стека. Если дизассемблировать приведенную выше программу, то будет видно, как это происходит:

0x080485f3 <main+95>: call 0x8048600 <cat4>; вызов функции

// пролог функции

0x08048600 <cat4+0>: push %ebp; сохранение ebp в стеке

0x08048601 <cat4+1>: mov %esp,%ebp

После этого в стеке отводится память для локальных переменных функции, в нашей программе это будет строка b. Так как в функции cat4 не выделяется место для каких-либо еще данных, то выделенное место в памяти для этой строки будет граничить с местом, где находится сохраненный адрес EBP и, соответственно, символ NULL, который вышел за границы памяти выделенной для переменной b будет перезаписывать один байт в этом адресе.

Содержание  Вперед на стр. 083-064-2