__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

此外,您实际上并不需要复制字符串,您只需mprotectVirtualProtect 它所在的页面即可使其可执行。但是如果你想确保它确实停在第一个 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 调用内存地址的主要内容,如果未能解决你的问题,请参考以下文章

访问对象内存地址

movl on x64 with GCC inline

在 gcc 中访问内联汇编中的字符串的地址

GCC 扩展内联汇编简介

JS之数据_变量_内存v(**V**)v

伪造 ASM 返回地址?