splincodewd
@splincodewd
Developer

Как затереть ссылку возврата?

Здравствуйте! Мне на учебе дали задание, чтобы я из одной функции вызвал другую, но сделал ее не очевидным способом. Дали подсказку: затереть ссылку возврата, но не совсем понятно как это реализовать можно?

Вот ошибочный пример:
#include <iostream>
using namespace std;
void bar(){
cout << "bar" << endl;
}

void foo(){
bar(); // это не правильно, так как явно вызвали сами, а нужно как-то вклинить в эту функцию
cout << "foo" << endl;
}

int main(){
foo(); // сделать должна быть вызвана bar(), хотя вызывали foo
return 0;
}


ProgramCallStack2_en.png
Нашел в интернете только картинку, как память выглядит с этими ссылками возврата, но опять же, как реализовать можно механизм такой, подскажите?

То есть, должно работать так:

#include <iostream>
using namespace std;
void bar(){
cout << "bar" << endl;
}

void foo(){
cout << "foo" << endl;
}

int main(){

foo(); // тут должен быть вызван bar(), хотя вызывали foo
       // что для этого можно сделать?

return 0;
}
  • Вопрос задан
  • 227 просмотров
Решения вопроса 2
@Mercury13
Программист на «си с крестами» и не только
Для чего это нужно? А для того, чтобы вы знали, как хакеры вас ломают. Простейший способ для этого — устроить переполнение буфера.
void foo()
{
  void* a[1];
  a[1] = (void*)&bar;
}

Может быть, придётся подменить не a[1], а a[2] или a[3].
Если так поступить, bar-то будет вызван, но по выходу из него, скорее всего, программа упадёт. Как сделать, чтобы не упала — оставлю домашним заданием. Высший пилотаж — завести в main() пару переменных, а потом через std::cout показать: вот они, целые и невредимые (компилируйте без оптимизации, макс. оптимизация может их просто заменить константами). Но тут уже придётся знать соглашение вызова и читать дизассемблерный листинг: от этого зависит, как вернуть стек в подходящее состояние.

UPD. Простите за моё незнание синтаксиса Си.
Ответ написан
@makaleks
Смотрите указатели на функции.
Если у вас есть указатель на функцию, то фактически, есть точка отсчёта для памяти программы. В принципе, для исследования этого достаточно. Для обхода памяти лучше использовать указатель на int - если компилируете под 32 бита, то вроде столько должен занимать размер стека. И конечно, 16-ричный вывод для удобства (одинаковое кол-во символов для любого числа). Да, и не пытайтесь вносить изменения - int const * в помощь!)
Попробуйте посмотреть ассемблерный листинг (опция в компиляторе), может получится вывести, какая команда в каком адресе лежит. Вы же поймёте значение команд call и ret?)
Ответ написан
Пригласить эксперта
Ответы на вопрос 1
PavelK
@PavelK
Я чёт то же не понял, но сделайте вызов через указатели...
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы