По-видимому, этот код пытается изменить стек таким образом, чтобы при возврате функции main
выполнение программы не возвращалось регулярно в библиотеку времени выполнения (что обычно завершало бы программу), а вместо этого переходило к коду, сохраненному в массиве shellcode
.
1) int *ret;
определяет переменную в стеке сразу под аргументами функции main
.
2) ret = (int *)&ret + 2;
позволяет переменной ret
указывать на int *
, который расположен двумя int
над 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 для другого объяснения этой темы.
person
stakx - no longer contributing
schedule
24.04.2010