#include<iostream.h>
#include<string.h>
#include<conio.h>
void F1(){
cout<<"hello"<<endl;
getch();
}
char mbf[13];
int main(){
char t[4];
for(int i=0;i<8;i++)mbf[i]=1;
((int*)(&mbf))[2]=int(&F1);
mbf[12]=0;
strcpy(t,mbf);
return 0;
}
Пример эксплуатации уязвимости «переполнение буфера в стеке».
Ну как "эксплуатации уязвимости" - скорее демонстрация того, как переполнение буфера, может поменять стек таким образом, что изменится поведение атакуемой программы. В данном случае программа "нападает" сама на себя (мазохизм), так что я не несу ответственности за какие-либо противоправные действия, которые вы можете совершить под влияниям данной статьи (или после доработки приводимых кодов). Приводимый материал и фрагмент кода призваны были показать студентам (т.е. я - показывал им), как важно контролировать границы записи при работе со строками.
Функция strcpy не контролирует размер переданных в неё данных, в связи с чем, копируемая строка mbf будет записана не только в отведенную область памяти строки t, но и далее, что перезапишет значение адреса возврата, хранящееся в стеке. После завершения (штатного!) функции копирования в регистры
EIP и
EBP будут вытолкнуты ложные (отличающиеся от сохранённых ранее) данные. Программа будет продолжена по адресу, помещённому в регистр
EIP (в данном случае адрес функции F1, которая нигде не вызывается явно).
Кто забыл основы архитектуры:
EIP____Адрес команды, которая будет выполнена процессором
ESP____Указывает на адреса данных, которые будут вынуты из стека
EBP____Указатель на базу (для удобства адресации)
При вызове функции main в стек будет помещено значение адреса возврата. Стек растёт вниз, т.е. самое первое значение, размещённое в стеке, имеет бОльший адрес (конкретные числовые значения нам не важны, но для удобства обозначим логически связанные группы байтов последовательностями, поименованными с префиксом #).
#A
#A
#A
#A
ESP хранит адрес вершины стека (т.е. на 4 байта ниже, т.к. они уже заняты адресом возврата из main). В EBP хранится
нечто.
Далее в стек выталкивается значение регистра EBP.
#A
#A
#A
#A
#B
#B
#B
#B
В регистр EBP сохраняется состояние (значение) регистра ESP. Затем в программе объявляется локальный массив размером 4 байта. Массив также записывается в стек. Регистр ESP уменьшается ещё на 4 байта. Регистр EBP по-прежнему хранит состояние регистра ESP на момент «начала» функции.
#A
#A
#A
#A
#B
#B
#B
#B
#M
#M
#M
#M
Теперь, если мы разместим в массиве нуль-терминированную строку «123» (элементы которой адресуются возрастающими индексами), то стек примет вид:
#A
#A
#A
#A
#B
#B
#B
#B
NULL
'3'
'2'
'1'
Дальнейшее присвоение значений элементам строки приведёт к перезаписи последовательности #B (значение регистра EBP на момент начала выполнения функции main), и #A (адрес возврата из функции main, который инструкция RET вытолкнет в регистр EIP).
Программа после копирования строк вызвает функцию F1, выход из которой крешит приложение (ещё бы - стек то расфигачили).
ЗЫЖ
SBJoker верно уточняет, что с хр сп2 в винде существует нативная защита от выполнения данных, а современные камни от интел и амд обладают и аппаратной.
Тем не менее, пример в посте работает и под сп3: фактически он выполняет не данные, а инструкции (т.к. в стек записали адрес функции, а не шеллкод) и, сверх того, не посягает на чужую память.