Antichat снова доступен.
Форум Antichat (Античат) возвращается и снова открыт для пользователей.
Здесь обсуждаются безопасность, программирование, технологии и многое другое.
Сообщество снова собирается вместе.
Новый адрес: forum.antichat.xyz
 |
|
Заметки по безопасному программированию на С |

10.09.2007, 18:24
|
|
Постоянный
Регистрация: 27.08.2006
Сообщений: 367
Провел на форуме: 2009677
Репутация:
472
|
|
Заметки по безопасному программированию на С
Это не статья в полном смысле этого слова, скорее это заметки по безопасному программированию на языке C. Моя цель - это рассмотрение наиболее популярных ошибок, которые можно встретить в приложениях на данном языке. Сразу скажу, что я не ставил перед собой задачу описать эксплутацию этих уязвимостей, так как это совсем другая тема. Прочитав этот материал, вы сможете анализировать свои программы на предмет описанных мной багов. Позже, вы научитесь самостоятельно определять уязвимые места в своем коде, даже больше того, у вас появится возможность дальше развиваться в направлении анализа кода. При написании я рассчитывал на средний уровень читающего, я старался писать доступным языком, но совсем банальные вещи, которые по моему мнению должен знать читатель, я не определял.
Содержание:
1) Переполнение буфера
2) Форматные строки
3) Уязвимость единичного смещения
и некорректное завершение строк
продолжение следует...
1) Переполнение буфера
Начнем с самого основного, уязвимости, связанные с переполнением буфера. Буфер может переполняться в самых разных местах памяти, включая стек и кучу.
Переполнение буфера обычно происходит, когда программа пытается записать данные за пределы конца буфера. Множество стандартных функций из crt не имеют никакого представления о размерах приемных буферов (strcpy, strcat, gets и тд). Несмотря на то, что переполнения можно избежать обычной проверкой перед вызовом функции, такие ошибки всеравно допускаются, хотя в чистом виде их можно встретить либо в совсем старых исходниках, либо в работах начинающих программистов. Отдельно стоит упомянуть и про то, что стандартные строковые функции имеют аналоги, которые позволяют ограничить размер записываемых данных. Таким образом, вместо strcpy, strcmp, и sprintf используются strncpy, strncmp, snprint, соответственно. Простой пример уязвимой программы:
Код:
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
char buffer[16];
if (argc < 2)
{
printf("Usage: %s arg", argv[0]);
return 1;
}
else
strcpy(buffer, argv[1]);
return 0;
}
Переполнение происходит из-за того, что функция strcpy не имеет понятия о размере буфера, таким образом, если argv[1] превысит размер buffer, то данные выйдут за границу приемного буфера и появиться возможность эксплутации программы. Теперь рассмотрим безопасный вариант:
Код:
#include <stdio.h>
#include <string.h>
#define SIZE 10
int main(int argc, char *argv[])
{
char buffer[SIZE];
if (argc < 2)
{
printf("Usage: %s arg", argv[0]);
return 1;
}
else
{
strncpy(buffer, argv[1], SIZE - 1);
buffer[SIZE - 1] = '\0';
}
return 0;
}
Здесь используется безопасный аналог функции strcpy, который четко задает размер копируемых данных и строка в конце завершается нулем, подробнее про нуль-сивол читайте ниже.
Тема довольно широко описана в большом количестве статей, поэтому я не стал уделять много внимания описанию данного вида уязвимостей. Стоит лишь упомянуть и про то, что производители операционных систем не стоят на месте и пытаюстя внедрить новые технологии по борьбе с данным классом уязвимостей. В windows доступны некоторые программные решения, которые предотвращают выполнение кода за пределами переполненного буфера, если такое переполнение было осуществлено. Среди этих решений - DEP в Windows XP SP2 и выше, который кстати существует и на аппаратном уровне. ОС такого вида называют системами с неисполняемым стеком. Думаю ни для кого не секрет, что все что создал человек можно взломать, DEP и прочие защиты не исключение. Советую ознакомиться с некоторыми работами по теме переполнения:
http://forum.antichat.ru/thread26791.html
http://shellcode.ru/index.php?name=News&file=article&sid=11
2) Форматные строки
Не смотря на то, что уязвимости форматных строк устаревают, я всеравно посчитал нужным рассказать об этом, так как и сейчас можно встретить уязвимые приложения, которые встречаются в основном в UNIX. Эта уязвимость базируется на том, что атакующий может контролировать форматную строку. Под форматной строкой понимается форматная строка которая передается функциям, получающим аргументы в стиле printf. Стоит атакующему получить контроль над форматной строкой и он получит возможность передать функцие спецификаторы, приводящие к самым разным результатам. Рассмотрим небольшой пример:
Код:
#include <stdio.h>
int main(int argc, char *argv[])
{
char buff[16], buff_[16];
printf("What is your name? ");
gets(buff);
sprintf(buff_ , "Hello, %s", buff);
printf(buff_);
return 0;
}
Теперь рассмотрим спецификаторы. Начнем с краткой информации по всем типам, которую я позаимствовал с википедии.
d, i — десятичное знаковое число, размер по-умолчанию, sizeof( int ). По-умолчанию записывается с правым выравниванием, знак пишется только для отрицательных чисел;
o — восьмеричное беззнаковое число, размер по-умолчанию sizeof( int );
u — десятичное беззнаковое число, размер по-умолчанию sizeof( int );
x и X — шестнадцатеричное число, x использует маленькие буквы (abcdef), X большие (ABCDEF), размер по-умолчанию sizeof( int );
f и F — числа с плавающей запятой. По-умолчанию выводятся с точностью 6, если число по модулю меньше единицы, перед десятичной точкой пишется 0. Величины ±∞ представляются в форме [-]inf или [-]infinity, Величина Nan представляется как [-]nan или [-]nan(любой текст далее). Использование F выводит указанные величины заглавными буквами (-INF, NAN). Аргумент по-умолчанию имеет размер double.
e и E — числа с плавающей запятой в экспоненциальной форме записи (вида 1.1e+44); e выводит символ «e» в нижнем регистре, E — в верхнем (3.14E+0);
g и G — число с плавающей запятой; форма представления зависит от значения величины (f или e);
a и A — число с плавающей запятой в шестнадцатеричном виде;
c — вывод символа с кодом, соответствующим переданному аргументу; переданное число приводится к типу usnigned char (или wint t, если был указан модификатор длины l);
s — вывод строки с нулевым завершающим байтом; если модификатор длины - l, выводится строка wchar_t*;
p — вывод указателя, внешний вид может существенно различаться в зависимости от внутреннего представления в компиляторе и платформе (например, 16 битная платформа MS-DOS использует форму записи вида FFEC:1003, 32-битная платформа с плоской адресацией использует адрес вида 00FA0030);
n — запись по указателю, переданному в качестве аргумента количество символов, записанных на момент появления командной последовательности, содержащей n;
% — символ для вывода знака процента (%), используется для возможности вывода символов процента в строке printf, всегда используется в виде %%.
Наиболее интересны для нас лишь несколько спецификаторов. Спецификатор %s базируется как указатель на строку, тоесть интерпретатор форматного вывода извлекает из стека парный ему аргумент, считая что это указатель на строку, понятно, что там может лежать вовсе не нужная нам строка. Отдельно упомяну нуль в конце, так как язык С использует ASCIIZ строки, то по стандарту строки должны закнчиваться нулем в конце. Вывод "мусорной" строки может привести к самым разным последствиям, в том числе к выводу произвольного дампа памяти и разрушению стека. Такой тип уязвимости называют атакой на строку форматирования. Откомпилируйте пример и введите заместо верного ответа спецификатор %s, обратите внимание на результат. Продолжим рассматривать спецификаторы, спецификатор %x выводит парное двойное слово из стека в шестнадцатиричном формате, это может пригодиться, если вам нужно получить адреса определенных данных. Также нужно сказать про спецификатор %n, цель которого записать в парный указатель количество выведенных байтов на данный момент. Перезаписываться будет не сам указатель, а область памяти, на которую он указывает. Это открывает огромные возможности нападающему. Ошибки форматной строки достаточно легко обнуружать в ходе анализа исходного кода, поэтому данный тип уязвимостей с каждым днем теряет свою актуальность.
3) Уязвимость единичного смещения
Уязвимость единичного смещения очень актуальна на данный момент, смсысл данной уязвимости состоит в том, что небольшое число байт записывается за пределы выделенной памяти. Чаще всего, именно один байт, в результате некорректного завершения строки нулем. Посмотрим на вызов функции strncat:
Код:
strncat(buf, tmp, sizeof(buf) - strlen(buf));
Ошибка единичного смещения в данном примере возникает из-за того, что strncat завершает выходную строку нулем, тоесть если третий аргумент данной функции не будет равен объему оставшегося места в выходном буфере с вычетом одного байта, то \0 запишется за пределами нашего буффера. Значит безопасный вызов функции будет таким:
Код:
strncat(buf, tmp, sizeof(buf) - strlen(buf) - 1);
Не стоит проверять на уязвимость только функции для работы со строками, обращайте внимание на все функции работы с памятью. Хотя эксплутировать данную уязвимость очень сложно, вероятность есть всегда. Далее рассмотрим ошибки некорректного заверешния строк в C, как известно ASCIIZ строки завершаются нулем, что не очень практично. Однако, от этого никуда не деться, поэтому разберемся с этим поподробнее. Допустим строка не заканчивается нулем, что тогда? Тогда все дальнейшее содержимое памяти будет рассматриваться как строка, до первого нуль-символа естественно. Последствия могут быть самыми разными, от включения в строку "мусора" до аварийного заверешния программы. Основная проблема кроется в тех самых функциях рантайма для работы со строками. Скажем strncpy не завершит строку нулем, если место в приемном буфере кончилось, поэтому нужно явно дописывать нуль-символ в конец строки. Нельзя недооценивать данную уязвимость, так как простор для действий атакующего ограничивается лишь рядом обстоятельств в виде рядом лежащих данных. Также обращаю ваше внимание на пропуск завершающего нуль-символа, скажем, если после пропуска завершителя произойдет запись данных - это может привести к фатальным последствиям, вплоть до выполнения произвольного кода.
Первые 3 пункта я написал, жду конструктивной критики и исправления логических и грамматических ошибок, так как писал не совсем в адекватном состоянии. Так как всю тему целиком охватить достаточно сложно, то я буду пополнять заметки со временем. Постараюсь ответить на любые нормальные вопросы по теме.
Последний раз редактировалось Ni0x; 11.09.2007 в 17:09..
|
|
|

10.09.2007, 18:54
|
|
Banned
Регистрация: 03.08.2007
Сообщений: 313
Провел на форуме: 951141
Репутация:
291
|
|
боян но про new\delete\malloc\realloc\free\ хотелось бы услышать....ну или ссылку на статью где изложены все подробности...
|
|
|

10.09.2007, 18:58
|
|
Постоянный
Регистрация: 08.05.2006
Сообщений: 816
Провел на форуме: 1845671
Репутация:
1338
|
|
Думаю это теоретическая статья. Было бы очень круто увидеть приведенные примеры программ и так сказать експлойты юзающие данные баги в защите. Это было бы очень наглядным к сказанному.
Ni0x Спс за статью!
|
|
|

10.09.2007, 18:58
|
|
Постоянный
Регистрация: 27.08.2006
Сообщений: 367
Провел на форуме: 2009677
Репутация:
472
|
|
inv, ну я же говорю, не надо писать мне боян и прочие выражения такого рода. Что именно ты хочешь услышать? Есть множество уязвимостей, основанных на функциях, связанных с выделением/освобождением памяти.
GlOFF, этот материал изначально позиционировался как теоритический, я специально не стал приводить примеры эксплутации данных уязвимостей, так как по каждому пункут есть свои нюансы и свои техники, рассматривать каждый аспект по отдельности с эксплутацией подобно написанию книги. У меня в планах есть написание статьи, где будет и практика и теория, но ее реализация будет после того, как я закончу с этими заметками.
Последний раз редактировалось Ni0x; 10.09.2007 в 19:14..
|
|
|

10.09.2007, 23:32
|
|
Постоянный
Регистрация: 08.05.2006
Сообщений: 816
Провел на форуме: 1845671
Репутация:
1338
|
|
Ni0x Очень жаль, ну ладно. Все же респект за статью!
|
|
|

11.09.2007, 00:36
|
|
Banned
Регистрация: 20.06.2005
Сообщений: 880
Провел на форуме: 4610226
Репутация:
1332
|
|
да все подобные статьи не актуальны и по сути уже не нужны. интересно было бы почитать про анализ существующего кода на ошибки, возможные в будущем ошибки, расчет архитектуры программы... естественно я не про анализ 5-строчечного кода говорю.
зы хорошую статью греат писал насчет вылова ошибок доступа к участкам памяти
|
|
|

11.09.2007, 00:40
|
|
Постоянный
Регистрация: 27.08.2006
Сообщений: 367
Провел на форуме: 2009677
Репутация:
472
|
|
ZaCo, зря ты так, на данный момент есть необкатанные типы уязвимостей, про которые ты наврятле найдешь нормальную информацию. А про анализ - это да, это я согласен. Думаю, решим.
|
|
|

11.09.2007, 08:16
|
|
Флудер
Регистрация: 27.12.2005
Сообщений: 2,372
Провел на форуме: 5339610
Репутация:
4360
|
|
если же такого аргумента не будет, интерпретатор начнет перебирать память до первого указателя, после чего начинает читать содержимое памяти по этому адресу, пока не дойдет до нуля или не попадет на память к которой у него нету доступа, в этом случае windows сгенерирует исключение.
Ой бред какойто. Аргумент извлекается в любом случае и НЕ БЫТЬ его там не может - это стек, там всегда есть данные (ну почти.. границу стека в данном случае не рассматриванием). Просто я к тому, что ничего перебирать он не будет - это бред.
А уж будет ли извлеченный дворд указателем - проблемы программиста
Откомпилируйте пример и введите заместо верного ответа спецификатор %s, обратите внимание на результат. Продолжим рассматривать спецификаторы, спецификатор %x выводит парное двойное слово из стека в шестнадцатиричном формате, это может пригодиться, если вам нужно получить адреса определенных данных. Также нужно сказать про спецификатор %n, цель которого записать в парный указатель количество выведенных байтов на данный момент. Перезаписываться будет не сам указатель, а область памяти, на которую он указывает. Это открывает огромные возможности нападающему.
Приведенный пример неуязвим же на этот тип атаки. Буфер не передается как форматная строка в данном случае. Стоит переписать так:
Код:
#include <stdio.h>
int main(int argc, char *argv[])
{
char buff[16];
printf("What is your name? ");
gets(buff);
printf("Hello, ");
printf(buff);
return 0;
}
Тогда действительно будут проблемы при вводе форматных символов.
Я видел книжку с отличным описанием данного типа атак. К сожалению, не помню ни названия, ни автора - читал у Cr4sh'а дома когда бухали по пьяни))
Чтото про эксплоиты
Последний раз редактировалось _Great_; 11.09.2007 в 08:19..
|
|
|

11.09.2007, 15:27
|
|
Участник форума
Регистрация: 06.04.2006
Сообщений: 257
Провел на форуме: 367179
Репутация:
76
|
|
IMHO, вместо замены strcpy на strncpy, лучше определять длину строки и если длина превышает размер буфера просто вывести ошибку.
|
|
|

11.09.2007, 15:38
|
|
Постоянный
Регистрация: 27.08.2006
Сообщений: 367
Провел на форуме: 2009677
Репутация:
472
|
|
TaNkist, про strncpy хотябы знать надо.
Заметки дополню и исправлю некоторые ошибки.
|
|
|
|
 |
|
|
Здесь присутствуют: 1 (пользователей: 0 , гостей: 1)
|
|
|
|