__asm__ gcc 调用内存地址
Posted
技术标签:
【中文标题】__asm__ gcc 调用内存地址【英文标题】:__asm__ gcc call to a memory address 【发布时间】:2014-03-13 02:15:34 【问题描述】:我有一个分配内存的代码,将一些缓冲区复制到分配的内存,然后跳转到该内存地址。
问题是我不能跳转到内存地址。我使用 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");
但它不起作用。 我该怎么做?
【问题讨论】:
将指针转换为函数指针并调用它? 在 AT&T 语法中,movl %eax, dest
是存储,而不是加载。
【参考方案1】:
通常不需要使用 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
它所在的页面即可使其可执行。但是如果你想确保它确实停在第一个 0
字节来测试你的 shellcode,那么一定要复制它。
如果你仍然坚持使用 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) 中,像这样从内联汇编进行函数调用是不安全的。无法在 RSP 下方的红色区域声明破坏者。
【讨论】:
它就像魔术一样工作!!你介意在回复中解释这个具体的例子吗?谢谢! ;)以上是关于__asm__ gcc 调用内存地址的主要内容,如果未能解决你的问题,请参考以下文章