−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
CTEPTOP 26.02.2017 22:47 # +1
inkanus-gray 26.02.2017 23:20 # +2
В Ideone не заработало. Нужно подогнать константы.
CTEPTOP 26.02.2017 23:21 # 0
CTEPTOP 26.02.2017 23:28 # 0
tmp := Pointer(Integer(ptr));
Так правильнее. Integer нельзя в Cardinal.
Код Ваш 100% рабочий. В консольке успевают мелькнуть строки "Hello, World" и "secret function", затем прога немедленно завершается, даже если добавить в код ReadLn.
inkanus-gray 26.02.2017 23:45 # 0
tmp := Pointer(ptr);
Приведение к Integer не нужно, это же указатель.
ReadLn нужно добавлять сразу после Writeln('secret function ', ...
Возврата из func1 в глобальный блок BEGIN...END. не будет, потому что адрес возврата затёрт.
inkanus-gray 27.02.2017 00:40 # +1
wano987 27.02.2017 10:37 # 0
log.txt %1 [enter]
Можешь не благодарить.
inkanus-gray 27.02.2017 11:35 # +1
CTEPTOP 27.02.2017 14:48 # +1
cykablyad 26.02.2017 23:56 # +2
.NET 4.5.2, x86, оптимизация выключена
cykablyad 27.02.2017 00:25 # +2
inkanus-gray 27.02.2017 01:03 # 0
Первая программа выводит только "Hello World!", вторая падает.
В .NET 3.5 обе выводят только "Hello World!".
inkanus-gray 27.02.2017 01:18 # 0
cykablyad 27.02.2017 06:14 # 0
cykablyad 26.02.2017 22:57 # +2
> long *ptr = (long *)&ptr + 3;
берем адрес на адрес возврата в main
> *ptr = (long)&func1;
меняем его на func1
> *((int *)ptr - 1) = 052;
суем 42 на место переменной var в стеке
после вывода secret function 42 выполнение main не продолжится, код ошибки будет хуй знает какой
cykablyad 26.02.2017 23:10 # 0
cykablyad 26.02.2017 23:27 # 0
Или я что-то неправильно посчитал
Psionic 26.02.2017 23:39 # 0
Я конечно не дохуя системщик, но как можно впихнуть что-то в локальную переменную, если функция в которой она использована еще не вызывалась и место в стеке под нее незарезервировано?
Pythoner 26.02.2017 23:48 # +3
inkanus-gray 26.02.2017 23:49 # +2
cykablyad 26.02.2017 23:50 # +1
У нас есть стек и указатель на него, esp
И есть указатель на текущий фрейм (кусок памяти на стеке, содержащий локальные переменные), обычно используют ebp его для хранения
В начале функции указатель на чужой фрейм (вызывающей функции) сохраняют и суют туда текущий указатель на стек, а сам стек смещают дальше, на размер фрейма
При этом сам фрейм нулями не затирается, и в нем могут попадаться куски предыдущих фреймов, например, которые успешно попадут в неинициализированные переменные
Это можно использовать, чтобы засунуть данные выше по стеку и использовать их как значения локальных переменных
gost 26.02.2017 23:04 # +1
> cout << "secret function " << var
> Результат его выполнения:
UB.
j123123 27.02.2017 10:39 # 0
inkanus-gray 27.02.2017 11:33 # +1
j123123 27.02.2017 12:50 # 0
inkanus-gray 27.02.2017 14:15 # 0
Инструкция calll @1 не генерирует ссылку на .text, потому что для JMP SHORT, JMP NEAR, CALL NEAR в x86 используется RIP-адресация.
&ptr скомпилируется в регистр esp со смещением, т. е. снова никаких указателей.
Правда, где-то я наглючил и у меня вылетает исключение...
inkanus-gray 27.02.2017 14:23 # 0
Теперь не вылетает. Но и не передаёт 42.
j123123 27.02.2017 16:07 # −1
inkanus-gray 27.02.2017 12:23 # 0
Теперь в eax у нас адрес метки @1. Адреса функций можно отсчитать от @1, посмотрев скомпилированный бинарник. Куда бы система ни загрузила бинарник, разность адресов между функциями одного бинарника не изменится даже при включенном ASLR.
cykablyad 27.02.2017 12:27 # +1
Вроде как функцию никто не вызывает, а текстики все равно выводятся
P.S. только недавно узнал, что ret может выталкивать аргументы из стека
inkanus-gray 27.02.2017 12:35 # +1
Можно сделать push func1; ret, но это уже будет эквивалент джампа.
P.S. Да, есть версия ret с параметром, указывающим, сколько байтиков из стека нужно «потерять».
cykablyad 27.02.2017 12:37 # 0
cykablyad 27.02.2017 12:42 # +1
А может и нет, sub esp же делается только если внутри функции стек трогают
inkanus-gray 27.02.2017 13:26 # +1
Без stack frame пролог выглядит так: А эпилог так:
То есть если у функции есть локальные переменные, придётся ещё корректировать значение указателя на стек. И такой эпилог: В эпилоге значение esp тупо затирается сохранённым ранее.
j123123 27.02.2017 12:51 # +1
guestinho 27.02.2017 11:06 # −1
http://ideone.com/nDaZq4
guest 28.02.2017 19:37 # 0