Повторная запись небольшого кода оболочки execve

Переход через http://hackoftheday.securitytube.net/2013/04/demystifying-execve-shellcode-stack.html

Я понял программу nasm, которая вызывает execve и пыталась перезаписать ее.

Некоторая справочная информация:

int execve(const char *filename, char *const argv[], char *const envp[]);

Итак, eax = 11 (номер вызова функции для execve), ebx должен указывать на char* filename, ecx должен указывать на argv[] (который будет таким же, как ebx, поскольку первый аргументом является сам *filename, например, "/bin/sh" ), а edx будет указывать на envp[] (null в этом случае).

Оригинальный код nasm:

global _start

section .text
_start:

xor eax, eax
push eax

; PUSH //bin/sh in reverse i.e. hs/nib//

push 0x68732f6e
push 0x69622f2f

mov ebx, esp

push eax
mov edx, esp

push ebx
mov ecx, esp

mov al, 11
int 0x80

Стек выглядит следующим образом:

enter image description here

Теперь я попытался оптимизировать это, сократив несколько инструкций. Я согласен, что до mov ebx, esp код останется прежним. Однако, поскольку ecx нужно указать на ebx, я могу переписать код следующим образом:

global _start

section .text
_start:

xor eax, eax
push eax

; PUSH //bin/sh in reverse i.e. hs/nib//

push 0x68732f6e
push 0x69622f2f
mov ebx, esp

mov ecx,ebx

push eax
mov edx, esp

mov al, 11
int 0x80

Тем не менее, я получаю ошибку сегментации, когда я запускаю свой переписанный код.

Мой стек выглядит следующим образом: enter image description here

Любые идеи, почему переписанный код не работает? Я также запускал gdb, и значения адреса соответствуют моему мнению, но он просто не будет работать.

Ответ 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.

Я также должен отметить, что обратный порядок символов в двух словах, используемых при построении строки, связан с конечной точкой машины, а не с направлением стека.