−13
- 01
- 02
- 03
- 04
- 05
- 06
- 07
- 08
- 09
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
#include <iostream>
using std::cout;
using std::cin;
using std::endl;
void func1()
{
int var;
cout << "secret function " << var << endl;
}
void printHelloWorld()
{
long *ptr = (long *)&ptr + 3;
*ptr = (long)&func1;
*((int *)ptr - 1) = 052;
cout << "Hello, World" << endl;
}
int main()
{
printHelloWorld();
return 0;
}
Результат его выполнения:
Hello, World
secret function 42
Вопросы:
1 Почему выводится secret function 42, если вызывается только printHelloWorld();
2 Откуда взялось число 42?
Подсказки:
1 Не потому что 42 - это ответ на «главный вопрос Жизни, Вселенной и Всего Остального»
2 На других архитектурах и компиляторах результат может быть другим, я компилил в linux gcc 5.4.0 x64
Топик на киберфоруме:
Простая и интересная задачка по C++: объяснить почему результат работы программы именно такой, какой он есть
Запостил:
Pythoner,
26 Февраля 2017
В Ideone не заработало. Нужно подогнать константы.
tmp := Pointer(Integer(ptr));
Так правильнее. Integer нельзя в Cardinal.
Код Ваш 100% рабочий. В консольке успевают мелькнуть строки "Hello, World" и "secret function", затем прога немедленно завершается, даже если добавить в код ReadLn.
tmp := Pointer(ptr);
Приведение к Integer не нужно, это же указатель.
ReadLn нужно добавлять сразу после Writeln('secret function ', ...
Возврата из func1 в глобальный блок BEGIN...END. не будет, потому что адрес возврата затёрт.
log.txt %1 [enter]
Можешь не благодарить.
.NET 4.5.2, x86, оптимизация выключена
Первая программа выводит только "Hello World!", вторая падает.
В .NET 3.5 обе выводят только "Hello World!".
> long *ptr = (long *)&ptr + 3;
берем адрес на адрес возврата в main
> *ptr = (long)&func1;
меняем его на func1
> *((int *)ptr - 1) = 052;
суем 42 на место переменной var в стеке
после вывода secret function 42 выполнение main не продолжится, код ошибки будет хуй знает какой
Или я что-то неправильно посчитал
Я конечно не дохуя системщик, но как можно впихнуть что-то в локальную переменную, если функция в которой она использована еще не вызывалась и место в стеке под нее незарезервировано?
У нас есть стек и указатель на него, esp
И есть указатель на текущий фрейм (кусок памяти на стеке, содержащий локальные переменные), обычно используют ebp его для хранения
В начале функции указатель на чужой фрейм (вызывающей функции) сохраняют и суют туда текущий указатель на стек, а сам стек смещают дальше, на размер фрейма
При этом сам фрейм нулями не затирается, и в нем могут попадаться куски предыдущих фреймов, например, которые успешно попадут в неинициализированные переменные
Это можно использовать, чтобы засунуть данные выше по стеку и использовать их как значения локальных переменных
> cout << "secret function " << var
> Результат его выполнения:
UB.
Инструкция calll @1 не генерирует ссылку на .text, потому что для JMP SHORT, JMP NEAR, CALL NEAR в x86 используется RIP-адресация.
&ptr скомпилируется в регистр esp со смещением, т. е. снова никаких указателей.
Правда, где-то я наглючил и у меня вылетает исключение...
Теперь не вылетает. Но и не передаёт 42.
Теперь в eax у нас адрес метки @1. Адреса функций можно отсчитать от @1, посмотрев скомпилированный бинарник. Куда бы система ни загрузила бинарник, разность адресов между функциями одного бинарника не изменится даже при включенном ASLR.
Вроде как функцию никто не вызывает, а текстики все равно выводятся
P.S. только недавно узнал, что ret может выталкивать аргументы из стека
Можно сделать push func1; ret, но это уже будет эквивалент джампа.
P.S. Да, есть версия ret с параметром, указывающим, сколько байтиков из стека нужно «потерять».
А может и нет, sub esp же делается только если внутри функции стек трогают
Без stack frame пролог выглядит так: А эпилог так:
То есть если у функции есть локальные переменные, придётся ещё корректировать значение указателя на стек. И такой эпилог: В эпилоге значение esp тупо затирается сохранённым ранее.
http://ideone.com/nDaZq4