我正在尝试使用GAS语法创建一个汇编程序,该程序可以在x86-64 arch上以位置无关的方式从.data
部分访问它的变量,强制执行32bit arch和IS(%eip
而不是%rip
)。
无论我尝试什么寄存器,我得到的最好的结果是Segmentation fault: 11
,甚至是访问EIP,我根本不应该做,因此SF。最好的结果,因为至少告诉我一些不是“meh,它不会做”的东西。
我正在使用gcc
在macOS 10.13.6 2010年中期英特尔酷睿2双核处理器上编译该文件(这就是为什么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已禁用。在代码签名的PIE中不允许绝对寻址(可能是-mdynamic-no-pic),但在/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值。
我查看了各种来源,但主要是英特尔语法或其中一个问题 - 1,2,3。我试图反汇编我能提出的最简单的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
的地址进入.data
部分。
然而,当我尝试使用var1
和_main
标签的相同方法时,没有:
.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
有什么想法我做错了什么?
编辑:
我设法从反汇编的C示例中删除了任何不必要的东西,最后得到了这个:
.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
对我来说这有点没有意义,因为根据this guide,调用者应该1)将参数推送到堆栈(无)2)call
标签和被调用者实际上应该使用ESP,EBP和其他寄存器。另外,为什么我甚至需要一个中间标签或更好的说,没有它有什么办法吗?
在32位模式下,没有像64位模式那样的eip
相对寻址模式。因此,代码就像
mov var(%eip), %eax
实际上并不合法,不会在32位模式下组装。 (在64位中,它会将地址截断为32位)。在传统的非PIE 32位二进制文件中,您可以这样做
mov var, %eax
它将var
的绝对地址的值移动到eax
,但这在PIE二进制文件中是不可能的,因为var
的绝对地址在链接时是未知的。
链接器知道的是二进制文件的布局以及标签之间的距离。因此,要访问全局变量,请按以下步骤操作:
var
的距离可以使用具有位移的寻址模式来组合步骤2和3。第1步很棘手。只有一条有用的指令告诉我们一个我们不知道的地址的地址是什么,那就是call
:call
指令将堆栈上下一条指令的地址推送,然后跳转到指定的地址。如果我们告诉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
它将返回地址移动到指定的寄存器。这是一个不遵循常规调用约定的特殊函数,eax
,ebx
,ecx
,edx
,esi
和edi
中的每一个都存在,具体取决于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实际上受到了影响。
最后请注意,不会跳过任何标签。我不太明白你对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编译器喜欢使用它们的原因。