我有一个分配内存的代码,将一些缓冲区复制到分配的内存,然后跳转到该内存地址。
问题是我无法跳转到内存地址。我正在使用 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)
,否则它可能是 optimized away as a dead store. . 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;
}
compiles with GCC9.1 -O2 -m32
to
func(char*, int):
jmp [DWORD PTR [esp+4]] # tailcall
此外,您实际上并不需要复制一个字符串,您可以只mprotect
或VirtualProtect
它所在的页面即 cocoa 执行。但是如果你想确保它确实在第一个 0
字节处停止以测试你的 shellcode,那么一定要复制它。
如果你仍然坚持内联汇编,你应该知道 gcc 内联汇编是一个复杂的东西。此外,如果您希望函数返回,您应该真正确保它遵循调用约定,特别是它保留了它应该保留的寄存器。
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
寄存器中的输出参数应分配给 block 后的变量 ret
"0"(dest)
= 输入参数与输出参数 0
(即 eax
)应从中加载dest
block 之前
"ecx", "edx"
= 告诉编译器这些寄存器可能会被 asm block 改变,按照正常的调用约定。
"memory"
= 告诉编译器 asm block 可能会对内存进行未指定的修改,因此不要缓存任何内容
请注意,在 x86-64 System V (Linux/OS X) 中,像这样从内联 asm 进行函数调用是不安全的。无法在 RSP 下面的红色区域声明 clobber。
关于c++ - __asm__ gcc 调用内存地址,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22367792/