Очевидно, этот код пытается изменить стек так, чтобы при возврате функции main
выполнение программы не возвращалось регулярно в библиотеку времени выполнения (которая обычно завершала бы программу), а вместо этого переходило в код, сохраненный в массив shellcode
.
1) int *ret;
определяет переменную в стеке сразу под аргументами main
функции.
2) ret = (int *)&ret + 2;
позволяет переменной ret
указывать на int *
, который помещается на два int
s выше ret
в стеке. Предположительно, именно там находится адрес возврата, где программа продолжит работу, когда вернется main
.
2) (*ret) = (int)shellcode;
Адрес возврата установлен на адрес содержимого массива shellcode
, поэтому содержимое shellcode
будет выполнено при возврате main
.
shellcode
, по-видимому, содержит машинные инструкции, которые, возможно, делают системный вызов для запуска /bin/sh
. Я могу ошибаться, поскольку на самом деле я не разбираю shellcode
.
P.S.: Этот код зависит от машины и компилятора и, возможно, не будет работать на всех платформах.
Ответ на второй вопрос:
и что будет, если я использую
ret = (int) & ret +2 и почему мы добавили 2?
почему не 3 или 4 ??? и я думаю, что Int
равно 4 байта, поэтому 2 будет 8 байтов нет?
ret
объявлен как int*
, поэтому присвоение ему int
(например, (int)&ret
) будет ошибкой. Что касается того, почему добавляется 2, а не любое другое число: очевидно, потому что этот код предполагает, что адрес возврата будет лежать в этом месте в стеке. Учтите следующее:
В этом коде предполагается, что стек вызовов увеличивается, когда на него что-то надавливается (как это действительно происходит, например, с процессорами Intel). По этой причине число добавляется , а не вычитается : адрес возврата находится по более высокому адресу памяти, чем автоматические (локальные) переменные (такие как ret
).
Из того, что я помню из моих дней сборки Intel, функцию C часто называют так: во-первых, все аргументы помещаются в стек в обратном порядке (справа налево). Затем функция вызывается. Таким образом, адрес возврата помещается в стек. Затем устанавливается новый кадр стека, который включает в себя помещение регистра ebp
в стек. Затем локальные переменные устанавливаются в стеке под всем, что было добавлено в него до этого момента.
Теперь я предполагаю следующий макет стека для вашей программы:
+-------------------------+
| function arguments | |
| (e.g. argv, argc) | | (note: the stack
+-------------------------+ <-- ss:esp + 12 | grows downward!)
| return address | |
+-------------------------+ <-- ss:esp + 8 V
| saved ebp register |
+-------------------------+ <-- ss:esp + 4 / ss:ebp - 0 (see code below)
| local variable (ret) |
+-------------------------+ <-- ss:esp + 0 / ss:ebp - 4
Внизу лежит ret
(32-разрядное целое число). Над ним находится сохраненный регистр ebp
(который также имеет ширину 32 бита). Над этим находится 32-битный адрес возврата. (Выше это будут main
аргументы - argc
и argv
- но они здесь не важны.) Когда функция выполняется, указатель стека указывает на ret
. Адрес возврата лежит на 64 бита "выше" ret
, что соответствует + 2
в
ret = (int*)&ret + 2;
Это + 2
, потому что ret
- это int*
, а int
- 32-битное, поэтому добавление 2 означает установку в ячейку памяти 2 раза; 32 бита (= 64 бита) выше (int*)&ret
... которое будет местоположением адреса возврата, если все предположения в вышеприведенном абзаце верны.
Экскурсия: Позвольте мне продемонстрировать на языке ассемблера Intel, как можно вызывать функцию C (если я правильно помню - я не гуру в этой теме, поэтому могу неправильно):
// first, push all function arguments on the stack in reverse order:
push argv
push argc
// then, call the function; this will push the current execution address
// on the stack so that a return instruction can get back here:
call main
// (afterwards: clean up stack by removing the function arguments, e.g.:)
add esp, 8
Внутри main может произойти следующее:
// create a new stack frame and make room for local variables:
push ebp
mov ebp, esp
sub esp, 4
// access return address:
mov edi, ss:[ebp+4]
// access argument 'argc'
mov eax, ss:[ebp+8]
// access argument 'argv'
mov ebx, ss:[ebp+12]
// access local variable 'ret'
mov edx, ss:[ebp-4]
...
// restore stack frame and return to caller (by popping the return address)
mov esp, ebp
pop ebp
retf
См. Также: Описание последовательности вызовов процедур в C для другого объяснения этой темы.