Вашего кода разбирает на что-то вроде этого:
00000000 31C0 xor eax,eax
00000002 50 push eax
00000003 682F2F7368 push dword 0x68732f2f
00000008 682F62696E push dword 0x6e69622f
0000000D 89E3 mov ebx,esp
0000000F 50 push eax
00000010 53 push ebx
00000011 89E1 mov ecx,esp
00000013 99 cdq
00000014 B00B mov al,0xb
00000016 CD80 int 0x80
ndisasm
предоставлен. Давайте рассмотрим эти инструкции шаг за шагом и проанализируем фрейм стека на этом пути.
xor eax,eax
регистрирует регистр eax
, поскольку операция XOR операнда с самим собой всегда будет давать нуль в результате. push eax
затем толкает значение в стек. Таким образом, стек в настоящее время выглядит более или менее, как это (смещения, показанными по отношению к значению esp
в начале кода, esp
означает ячейку стека, которая esp
в настоящее время указывает на):
+----------+
0 | 00000000 |
esp -4 | xxxxxxxx |
+----------+
Далее, мы имеем два push dword
инструкции, которые проталкивают некоторое непосредственное значение в стек, который - после их выполнения - выглядит следующим образом:
+----------+
0 | 00000000 |
-4 | 68732f2f |
-8 | 6e69622f |
esp -12| xxxxxxxx |
+----------+
esp
в настоящее время указывает на последний байт второго непосредственного значения, что была отодвинута в стек. Попробуем интерпретировать перенесенные значения как ASCII в том порядке, в котором они будут считаны из стека, если мы начнем последовательно с текущего значения esp
. Мы получаем байтовую последовательность 2f62696e2f2f7368
, которая в ASCII равна /bin//sh
. Кроме того, последовательность заканчивается на 0, поэтому она является допустимой C-строкой.
Это основная причина, по которой текущее значение esp
сохраняется в регистре ebx
.Он содержит путь к исполняемому файлу, который будет запущен. Двойная косая черта не является проблемой для ОС, поскольку POSIX просто игнорирует множественные вхождения косых черт и рассматривает их как одну косую черту.
Затем у нас есть текущие значения eax
и ebx
, помещенные в стек. Мы знаем, что eax
содержит нуль, а ebx
содержит указатель на C-строку "/bin//sh"
. Стек в настоящее время выглядит следующим образом:
+----------+
0 | 00000000 |
-4 | 68732f2f |
-8 | 6e69622f |
ebx -12| 00000000 |
-16| (ebxVal) |
ecx esp -20| xxxxxxxx |
+----------+
После нажатия значения регистров в стек, текущий указатель esp
сохраняется в ecx
.
cdq
- инструкция, выполняющая очень аккуратный трюк в этом случае: знак-расширяет текущее значение eax
в парную регистрацию edx:eax
. Поэтому в этом случае он обнуляет значение в edx
, так как знак расширения нуля равен нулю. Разумеется, мы могли бы очистить значение в edx
с помощью xor edx, edx
, но эта инструкция кодируется двумя байтами - и cdq
занимает только один.
Следующая инструкция помещает значение 0xb
(11) в регистр нижнего байта eax
. Точно так же, как и в предыдущем случае, мы могли бы просто сделать mov eax, 0xb
, но это приведет к 5-байтовой инструкции, так как непосредственные должны быть закодированы как полные 32-битные значения.
int 0x80
вызывает вызов системного вызова в Linux. Он ожидает номер системного вызова в eax
(который теперь равен 0xb
, поэтому функция sys_execve
будет называться), а также дополнительные аргументы в ebx
, ecx
, edx
, esi
, edi
и ebp
.
Теперь давайте посмотрим на прототип для этого системного вызова:
int execve(const char *filename, char *const argv[], char *const envp[]);
Поэтому filename
аргумент помещается в ebx
- это указывает на /bin//sh
. argv
, размещенный в ecx
, представляет собой массив аргументов для исполняемого файла, который должен быть выполнен, и должен быть прерван значением NULL
. В архитектуре Intel NULL
равен 0
, а ecx
указывает на это: указатель на /bin//sh
, а затем значение NULL
. envp
, который равен NULL
, указывает на массив значений среды, который должен быть выражен как char*
значения формы key=value
.
Успешное выполнение execve
приводит к тому, что текущий образ процесса заменяется изображением исполняемого файла с указателем, выполняемого с предоставленными аргументами. В этом случае выполняется /bin/sh
(если оно существует) с аргументом /bin//sh
.
Возможно, Майкл был прав, почему это не работает: последние ядра Linux маркируют страницы данных как неисполняемые, и попытка их выполнения приведет к ошибке сегментации.
Ваша система, вероятно, отмечает страницы, в которых ваш стек находится как неисполняемый. В отладчике вы увидите ошибку сегментации во время команды 'call' с адресом' buf' в качестве параметра. Влияет ли ваше задание на обход этой защиты? –
@MichaelFoukarakis Я намеревался добавить это, но было уже поздно! Теперь я отредактировал вопрос с дополнительной информацией, которая должна ответить на этот вопрос, на мой взгляд. –
Ну, проблема, с которой вы сталкиваетесь, не имеет ничего общего с рассматриваемым кодом. Может быть, вы хотите опубликовать цель своего задания? –