Ответ 1
В обоих случаях ebx указывает на строку "//bin/sh". Эквивалент C-кода выглядит следующим образом:
char *EBX = "//bin/sh";
Но в первом примере ecx установлен на адрес указателя на эту строку. Эквивалент C-кода выглядит следующим образом:
char *temp = "//bin/sh"; // push ebx
char **ECX = &temp; // mov ecx, esp
В вашем втором примере ecx просто установлен на то же значение, что и ebx.
char *ECX = "//bin/sh";
Таким образом, два примера принципиально отличаются друг от друга: ecx имеет два совершенно разных типа и значения.
Update:
Я должен добавить, что технически ecx представляет собой массив указателей char (аргумент argv), а не только указатель на указатель char. Фактически вы создаете массив из двух элементов в стеке.
char *argv[2];
argv[1] = NULL; // push eax, eax being zero
argv[0] = "//bin/sh"; // push ebx
ECX = argv; // mov ecx,esp
Только половина этого массива удваивается как аргумент envp. Поскольку envp - это единственный массив элементов с единственным элементом, установленным в NULL, вы можете думать, что аргументы envp устанавливаются с помощью кода C следующим образом:
EDX = envp = &argv[1];
Это достигается установкой edx в esp, в то время как массив argv создается только наполовину. Объединив код для двух заданий вместе, вы получите следующее:
char *argv[2];
argv[1] = NULL; // push eax, eax being zero
EDX = &argv[1]; // mov edx,esp
argv[0] = "//bin/sh"; // push ebx
ECX = argv; // mov ecx,esp
Это немного запутанно, но я надеюсь, что это имеет смысл для вас.
Обновление 2
Все аргументы execve
передаются как регистры, но эти регистры являются указателями на память, которые нужно выделить где-то - в этом случае - в стеке. Поскольку стек строит вниз в памяти, куски памяти должны быть построены в обратном порядке.
Память для трех аргументов выглядит так:
char *filename: 2f 2f 62 69 | 6e 2f 73 68 | 00 00 00 00
char *argv[]: filename | 00 00 00 00
char *envp[]: 00 00 00 00
Имя файла создается следующим образом:
push eax // '\0' terminator plus some extra
push 0x68732f6e // 'h','s','/','n'
push 0x69622f2f // 'i','b','/','/'
Аргумент argv выглядит следующим образом:
push eax // NULL pointer
push ebx // filename
И аргумент envp выглядит так:
push eax // NULL pointer
Но, как я уже сказал, исходный пример решил поделиться памятью между argv и evp, поэтому нет необходимости в этом последнем push eax
.
Я также должен отметить, что обратный порядок символов в двух словах, используемых при построении строки, связан с конечной точкой машины, а не с направлением стека.