Издательский дом ООО "Гейм Лэнд"СПЕЦВЫПУСК ЖУРНАЛА ХАКЕР #45, АВГУСТ 2004 г.

Защитись и замети!

Коваленко Дмитрий aka Ingrem

Спецвыпуск Xakep, номер #045, стр. 045-068-2


return 0; }

int main() {

char big[255];

puts("Enter string:");

gets(big);

overflow_proc(big);

puts("No overflow ;-)");

return 0; }

Этот исходник – своего рода классика. Он упоминается практически во всех статьях про переполнения буфера, и чаще всего именно на нем юные хакеры оттачивают свое умение писать эксплоиты ;-). Отключим автоматический контроль переполнения стека и откомпилируем исходник. Запустим overflow.exe. Теперь, если по запросу "Enter string:" ввести строку длиннее 10 символов, приложение вылетит с ошибкой:

Аварийное завершение происходит по очень простой причине: в процедуре overflow_proc строка копируется из big в buff с помощью стандартной функции strcpy. Проблема в том, что strcpy не проверяет, помещается ли строка в buff, и затирает стековый фрейм. Как этого избежать? Тут есть два пути. Во-первых, прежде чем копировать строку в буфер, можно просто проверить ее длину. Если она больше размера буфера, то не копировать ее вообще. Проверку можно организовать, например, так:

Листинг

int overflow_proc(char* big) {

char buff[10];

if(strlen(big)>10) {

strcpy(buff, big);

} else

{ // а-а-ааа! спасите, взламывают!

puts("Overflow attack detected!");

}; return 0; }

Второй путь – использовать безопасные функции. В C++ многие стандартные функции не проверяют длину строки. Это касается, например, strcpy, gets, sprintf. Поэтому вместо них лучше использовать безопасные аналоги: strncpy, fgets, snprintf. При использовании безопасной строковой функции программист должен в параметрах передать максимальный размер строки:

Листинг

int overflow_proc(char* big) {

char buff[10];

strncpy(buff, big, 9);

return 0; }

Если строка в big будет больше 9 байт, в buff скопируются только первые 9 байт из big. Остальная часть строки ("хвост") будет обрезана без каких-либо предупреждений. Кстати, о безопасных функциях. В нашем примере, если хакер введет больше 255 символов, опять возникнет переполнение, но уже в буфере big. Так что лучше в функции main заменить gets на безопасную fgets:

Листинг

int main() {

char big[255];

FILE *stream = fopen("CON:", "r");

puts("Enter string:");

fgets(big, 254, stream);

overflow_proc(big);

return 0; }

Ну, вот вроде бы и все, теперь наше приложение может себя защитить. Правда несложно? Едем дальше.

А если не строка?..

Действительно, а что если в буфер заносится не строка с завершающим нулем, а просто цепочка байт? Данные произвольной структуры, среди которых вполне может оказаться пара-тройка нулей? Совет тут один – проверяй длину данных самостоятельно.

Есть два общих правила, срабатывающих в 99% случаев. Первое. Многие функции, с помощью которых производится чтение в буфер, позволяют задавать количество читаемых байт. Следи, чтобы это количество не превысило размер буфера! Например, ты считываешь данные из файла с помощью API _lread в буфер величиной 255 байт. Всегда проверяй, чтобы последний параметр _lread был не больше этого значения. Второе. Если длину данных нельзя задать явно – ее можно узнать с помощью какой-то специальной функции! Например, ты читаешь из сокета с помощью API recv. Чтобы узнать количество байт, используй API ioctlsocket. Другой пример: ты преобразовываешь обычную строку в UNICODE. Чтобы узнать, какой буфер для этого понадобится, вызови API MultiByteToWideChar с нулевым последним параметром. Примеров можно привести много. Думай головой и не ленись заглядывать в MSDN.

Назад на стр. 045-068-1  Содержание  Вперед на стр. 045-068-3