Общая хитрость связана с тем, как код и переменные программы размещаются в памяти. Например, когда вызывается функция, программа (код, вставленный компилятором) должна хранить адрес инструкции, к которой нужно вернуться. Так что, если это 32-битное слово перед началом стека, можно сделать:
void foo()
{
int array[5];
int var = 0;
int var2 = 0;
// read in user input
printf("Enter index and value to write:");
scanf("%i", var);
scanf("%i", var2);
// malicious user might set var to -1 and var2 to an address to execute
// if say the 32-bit value before the stack variables is the instruction to
// return to
array[var] = var2
// return now goes to malicious code
}
(Таким образом, ваша задача - создать код, чтобы это было невозможно. :))
Правила реализации вызова функции, распределения переменных стека, передачи значений и возврата возвращаемых значений называются соглашением о вызовах . Я рекомендую прочитать прилагаемую статью, чтобы лучше и глубже охватить соглашения о вызовах языка Си.