У меня есть код, который выделяет память, копирует некоторый буфер в выделенную память, а затем перескакивает на этот адрес памяти.
проблема в том, что я не могу перейти на адрес памяти. Я использую gcc и __asm__
но я не могу назвать этот адрес памяти.
Я хочу сделать что-то вроде:
address=VirtualAlloc(NULL,len+1, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
dest=strncpy(address, buf, len);
И тогда я хочу сделать это в ASM:
MOV EAX, dest
CALL EAX.
Я пробовал что-то вроде:
__asm__("movl %eax, dest\n\t"
"call %eax\n\t");
Но это не работает. Как мне это сделать?
Обычно для этого не нужно использовать asm, вы можете просто просмотреть указатель на функцию и позволить компилятору позаботиться о деталях.
Вам нужно использовать __builtin___clear_cache(buf, buf+len)
после копирования машинного кода в буфер, прежде чем разыменовать указатель на функцию, иначе он может быть оптимизирован как мертвое хранилище. , В x86 имеется согласованный кэш инструкций, поэтому он не компилируется ни с какими дополнительными инструкциями, но он все еще нужен, чтобы оптимизатор знал, что происходит.
static inline
int func(char *dest, int len) {
__builtin___clear_cache(dest, dest+len); // no instructions on x86 but still needed
int ret = ((int (*)(void))dest)(); // cast to function pointer and deref
return ret;
}
компилируется с GCC9.1 -O2 -m32
в
func(char*, int):
jmp [DWORD PTR [esp+4]] # tailcall
Кроме того, вам на самом деле не нужно копировать строку, вы можете просто mprotect
или VirtualProtect
страницу, чтобы сделать ее исполняемой. Но если вы хотите убедиться, что он останавливается на первом 0
байте для проверки вашего шелл-кода, то обязательно скопируйте его.
Если вы все же настаиваете на inline asm, вы должны знать, что gcc inline asm - сложная вещь. Кроме того, если вы ожидаете, что функция вернется, вы должны убедиться, что она соответствует соглашению о вызовах, в частности, она сохраняет регистры, которые должны.
Синтаксис AT & T - это op src, dst
поэтому ваш mov
фактически был хранилищем глобального символа dest
.
Тем не менее, вот ответ на вопрос как сформулировано:
int ret;
__asm__ __volatile__ ("call *%0" : "=a" (ret) : "0" (dest) : "ecx", "edx", "memory");
Объяснение: https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html.
call *%0
= %0
относится к первому замещенному аргументу, *
- стандартный синтаксис gas
для косвенного вызова
"=a" (ret)
= выходной аргумент в регистре eax
должен быть назначен переменной ret
после блока
"0" (dest)
= входной аргумент в том же месте, что и выходной аргумент 0
(который является eax
) должен быть загружен из dest
перед блоком
"ecx", "edx"
= сообщают компилятору, что эти регистры могут быть изменены блоком asm согласно обычному соглашению о вызовах.
"memory"
= сообщить компилятору, что блок asm может внести неопределенные изменения в память, поэтому ничего не кэшируйте
Обратите внимание, что в x86-64 System V (Linux/OS X) небезопасно выполнять вызов функции из встроенного asm, как это. Там нет никакого способа объявить клоббер на красной зоне ниже RSP.
movl %eax, dest
- это хранилище, а не загрузка.