具有LEA指令的GAS ASM PIE x86-64访问变量

时间:2018-09-15 11:37:21

标签: assembly x86 gas position-independent-code

我正在尝试使用GAS语法创建一个汇编程序,该程序可以通过执行 32bit arch和IS在x86-64 arch上以与位置无关的方式从.data节访问其变量({%eip而不是%rip)。

无论我尝试了什么寄存器,我得到的最佳结果都是Segmentation fault: 11,甚至那是访问EIP的全部结果,因此是SF。最好的结果,因为那至少告诉了我“嗯,它不会做”以外的其他东西。

我正在2010年中的macOS 10.13.6英特尔酷睿2 Duo上使用gcc编译文件(这就是clang可能的原因):

$ gcc --version
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/usr/include/c++/4.2.1
Apple LLVM version 9.1.0 (clang-902.0.39.2)
Target: x86_64-apple-darwin17.7.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin

,并通过以下方式将一些选项传递给链接器:

gcc -m32 -Wl,-fatal_warnings,-arch_errors_fatal,-warn_commons,-pie test.s
  

ld:警告:禁用PIE。绝对寻址(也许是-mdynamic-no-pic)在代码签名的PIE中是不允许的,但在/whatever.../test-a07cf9.o的_main中使用。要解决此警告,请不要使用-mdynamic-no-pic进行编译,也不要使用-Wl,-no_pie进行链接   ld:致命警告引起的错误(-fatal_warnings)   clang:错误:链接器命令失败,退出代码为1(使用-v查看调用)   1


test.s

.text
.global _main

_main:
    xor %eax, %eax
    xor %ebx, %ebx

    # lea var1(%esi/edi/ebp/esp), %ebx  # can't compile, not PIE
    # lea var1(%eip), %ebx  # segfault, obvs

    # lea (%esp), %ebx      # EBX = 17
    # lea (%non-esp), %ebx  # segfault

    # lea 0(%esi), %ebx     # segfault 
    # lea 0(%edi), %ebx     # segfault
    # lea 0(%ebp), %ebx     # EBX = 0
    # lea 0(%esp), %ebx     # EBX = 17
    # lea 0(%eip), %ebx     # segfault, obvs

    movl (%ebx), %eax
    ret

.data
    var1: .long 6

.end

我正在使用./a.out; echo $?运行它,以检查最后来自ret的EAX值。

我查看了各种来源,但主要是Intel语法或以下问题之一-123。我试图反汇编最简单的C示例,例如return-main()中的全局变量+ gcc -S test.c -fPIE -pie -fpie -m32

int var1 = 6;
int main() { return var1; }

这基本上导致:

    .section    __TEXT,__text,regular,pure_instructions
    .macosx_version_min 10, 13
    .globl  _main                   ## -- Begin function main
    .p2align    4, 0x90
_main:                                  ## @main
    .cfi_startproc
## BB#0:
    pushl   %ebp
Lcfi0:
    .cfi_def_cfa_offset 8
Lcfi1:
    .cfi_offset %ebp, -8
    movl    %esp, %ebp
Lcfi2:
    .cfi_def_cfa_register %ebp
    pushl   %eax
    calll   L0$pb
L0$pb:
    popl    %eax
    movl    $0, -4(%ebp)
    movl    _var1-L0$pb(%eax), %eax
    addl    $4, %esp
    popl    %ebp
    retl
    .cfi_endproc
                                        ## -- End function
    .section    __DATA,__data
    .globl  _var1                   ## @var1
    .p2align    2
_var1:
    .long   6                       ## 0x6


.subsections_via_symbols

这显然使用MOV作为LEA,并且与我的指令几乎相同,除了-L0$pb部分,该部分应为+/-格式,例如_var1的地址-L0$pb的地址才能进入{ {1}}部分。

但是,当我尝试使用带有.datavar1标签的相同方法时,什么也没有:

_main

有什么主意我在做错什么吗?

编辑:

我设法从反汇编的C示例中删除了所有不必要的内容,并最终得到了这样的结果:

.text
.global _main

_main:
    xor %eax, %eax
    xor %ebx, %ebx

    #movl var1-_main(%ebp), %eax  # EAX = 191
    #movl var1-_main(%esp), %eax  # EAX = 204
    #movl var1-_main(%eax), %eax  # segfault
    ret

.data
    var1: .long 6

.end

这对我来说意义不大,因为根据this guide,调用者应1)将参数压入堆栈(无)2).text .global _main _main: pushl %ebp pushl %eax calll test test: popl %eax /* var1, var2, ... */ movl var1-test(%eax), %eax addl $4, %esp popl %ebp retl /** * how var1(label) - test(label) skips this label * if it's about address subtracting? */ blobbbb: xor %edx, %edx .data var1: .long 6 var2: .long 135 标签和 callee 实际上应该与ESP,EBP和其他寄存器一起玩。另外,为什么我什至需要一个中间标签或更好的说法,没有它还有什么办法?

1 个答案:

答案 0 :(得分:4)

在32位模式下,没有像64位模式那样的eip相对寻址模式。因此,类似

的代码
mov var(%eip), %eax

实际上不是合法的,并且不能以32位模式进行汇编。 (在64位中,它将地址截断为32位)。在传统的非PIE 32位二进制文​​件中,您只会这样做

mov var, %eax

将绝对地址var的值移动到eax,但是在PIE二进制文件中这是不可能的,因为在链接时var的绝对地址是未知的。

链接器所知道的是二进制文件的布局以及标签之间的距离。因此,要访问全局变量,请按以下步骤操作:

  1. 找出某些标签的绝对地址并为其加载一些寄存器
  2. 加上该标签到var的距离
  3. 访问变量

可以使用带位移的寻址模式将步骤2和3组合在一起。步骤1很棘手。只有一条有用的指令告诉我们不知道其地址的位置的地址是什么,即callcall指令将下一条指令的地址压入堆栈,然后跳转到指定的地址。如果我们告诉call仅跳转到下一个地址,则会将其功能降低为本质上push %eip的内容:

        call Label                  # like push %eip
Label:  ...

请注意,此用例在CPU的返回预测中是特殊情况,实际上并未计为函数调用。由于这不是真正的函数调用,因此我们没有建立堆栈框架或类似的框架,并且此调用没有返回值。这只是一种获取指令指针值的机制。

因此,我们由此知道Label的地址。接下来,我们可以将其弹出堆栈,并使用它来找到var的地址:

        call Label
Label:  pop %eax                    # eax = Label
        add $var-Label, %eax        # eax = Label + var - Label = var

然后我们可以取消引用以获得var的内容:

        call Label
Label:  pop %eax
        add %eax, $var-Label
        mov (%eax), %eax            # eax = *var

在真实代码中,您将合并加法和内存操作数以保存指令:

        call Label
Label:  pop %eax
        mov var-Label(%eax), %eax   # eax = *var

如果要在一个函数中引用多个静态变量,则只需使用一次即可。只需使用适当的差异即可:

        call Label
Label:  pop %eax
        mov foo-Label(%eax), %ebx   # ebx = *foo
        mov bar-Label(%eax), %ecx   # ecx = *bar

请注意,gcc支持此惯用法的变体来获取指令指针的内容。它创建了很多这样的函数:

___x86.get_pc_thunk.bx:
        mov (%esp), %ebx
        ret

将返回地址移至指定的寄存器。这是一个不遵循常规调用约定的特殊功能,eaxebxecxedxesiedi,取决于gcc要使用哪个寄存器。代码如下:

        call ___x86.get_pc_thunk.bx # ebx = Label
Label:  mov foo-Label(%ebx), %eax   # eax = *foo
        mov bar-Label(%ebx), %ecx   # ecx = *bar

gcc使用此代码在CPU上获得更好的性能,这些CPU的返回预测未考虑此假调用习惯。我不知道实际上哪些CPU受了影响。

最后请注意,没有标签会被跳过。我不太了解您对blobbbb的意思。哪个控件应该达到此标签?

最后,您的示例应如下所示:

        .text
        .global _main

_main:  call Label                  # push %eip
Label:  pop %eax                    # eax = Label
        mov var1-Label(%eax), %eax  # eax = *(Label+var1-Label)
        ret


        .data
var1:   .long 6

请注意,永远不需要.end指令。以大写字母L开头的标签是不以符号表结尾的局部标签,这就是C编译器喜欢使用它们的原因。