【问题标题】:How get EIP from x86 inline assembly by gcc如何通过 gcc 从 x86 内联汇编中获取 EIP
【发布时间】:2019-08-09 02:21:00
【问题描述】:

我想从下面的代码中获取EIP的值,但是编译不通过

命令: gcc -o xxx x86_inline_asm.c -m32 && ./xxx

文件内容 x86_inline_asm.c:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main()
{
    unsigned int eip_val;
    __asm__("mov %0,%%eip":"=r"(eip_val)); 
    return 0;
}

如何使用内联汇编获取EIP的值,在x86下可以编译成功。 如何修改代码并使用命令完成?

【问题讨论】:

  • 没有mov foo, eipmov eip, foo指令。

标签: gcc assembly x86 inline-assembly


【解决方案1】:

这听起来不太可能有用(相对于像void *tmp = main 这样只获取整个函数的地址),但它是可能的。


只需获取标签地址,或使用.(当前行的地址),让链接器担心将正确的立即数转换为机器代码。所以你不是在架构上阅读EIP,只是从即时读取它当前的价值。

asm volatile("mov $., %0" : "=r"(address_of_mov_instruction) );

AT&T 的语法是mov src, dst,所以你写的如果组装起来就是一个跳转。

(在架构上,EIP = 一条指令在执行时的 end,所以可以说你应该这样做

asm volatile(
  "mov $1f, %0  \n\t"      // reference label 1 forward
  "1:"               // GAS local label
  "=r"(address_after_mov)
);

我正在使用asm volatile,以防这个 asm 语句通过内联或其他方式在同一个函数中多次重复。如果您希望每个案例获得不同的地址,它必须是volatile。否则,编译器可以假定此 asm 语句的所有实例都产生相同的输出。一般这样就好了。


在 32 位模式下,您没有 LEA 的 RIP 相对寻址,因此实际读取 EIP 的唯一好方法是调用/弹出。 Reading program counter directly。它不是通用寄存器,因此您不能只将其用作mov 或任何其他指令的源或目标。


但实际上你根本不需要内联汇编。 Is it possible to store the address of a label in a variable and use goto to jump to it? 展示了如何使用 GNU C 扩展,&amp;&amp;label 获取其地址。

int foo;
void *addr_inside_function() {
    foo++;

    lab1:  ;  // labels only go on statements, not declarations
    void *tmp = &&lab1;

    foo++;
    return tmp;
}

在函数之外你不能安全地使用这个地址;我把它作为一个例子返回,让编译器在 asm 中放置一个标签,看看会发生什么。如果该标签没有 goto,它仍然可以非常积极地优化函数,但您可能会发现它可用作函数中其他位置的 asm goto(...) 的输入。

但无论如何,它on Godbolt 编译为这个asm

# gcc -O3 -m32
addr_inside_function:
.L2:
        addl    $2, foo
        movl    $.L2, %eax
        ret
#clang -O3 -m32
addr_inside_function:
        movl    foo, %eax
        leal    1(%eax), %ecx
        movl    %ecx, foo
.Ltmp0:                                 # Block address taken
        addl    $2, %eax
        movl    %eax, foo
        movl    $.Ltmp0, %eax        # retval = label address
        retl

所以 clang 加载全局,计算 foo+1 并存储它,然后在标签计算 foo+2 并存储它之后。 (而不是加载两次)。因此,您仍然无法从任何地方有效地跳转到标签,因为它取决于 fooeax 中的旧值,以及存储 foo+2 所需的行为

【讨论】:

    【解决方案2】:

    我不知道这个的 gcc 内联汇编语法,但是对于 masm:

            call    next0
    next0:  pop     eax           ;eax = eip for this line
    

    在 Masm 的情况下,$ 表示当前位置,并且由于 call 是一个 5 字节的指令,没有标签的替代语法是:

            call    $+5
            pop     eax
    

    【讨论】:

    猜你喜欢
    • 2015-05-19
    • 1970-01-01
    • 2010-10-07
    • 2012-10-20
    • 1970-01-01
    • 1970-01-01
    • 2010-09-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多