我正试图在Linux环境下,在C文件中具有内联汇编的x86_64机器上,通过syscall
指令调用sys_write
。为了实现这一点,我编写了以下代码:
[tjm@ArchPad poc]$ cat poc.c
const char S[] = "abcdn";
int main() {
__asm __volatile("
movq $1, %%rax;
movq $1, %%rdi;
movq %0, %%rsi;
movq $5, %%rdx;
syscall;"
:
: "i"(S)
);
return 0;
}
当我用GCC编译它时,出现了以下错误:
[tjm@ArchPad poc]$ gcc -O0 poc.c -o poc
/usr/bin/ld: /tmp/ccWYxmSb.o: relocation R_X86_64_32S against symbol `S' can not be used when making a PIE object; recompile with -fPIE
collect2: error: ld returned 1 exit status
然后我将-fPIE
添加到编译器命令中。不幸的是,一切都没有改变:
[tjm@ArchPad poc]$ gcc -fPIE -O0 poc.c -o poc
/usr/bin/ld: /tmp/ccdK36lx.o: relocation R_X86_64_32S against symbol `S' can not be used when making a PIE object; recompile with -fPIE
collect2: error: ld returned 1 exit status
这太奇怪了,以至于我很好奇手动编译和链接它:
[tjm@ArchPad poc]$ gcc -fPIE -O0 poc.c -S
[tjm@ArchPad poc]$ as -O0 poc.s -o poc.o
[tjm@ArchPad poc]$ ld -O0 -melf_x86_64 --dynamic-linker /usr/lib/ld-linux-x86-64.so.2 /usr/lib/crt{1,i,n}.o -lc poc.o -o poc
令人惊讶的是,在上述尝试过程中没有出现错误。然后我测试了可执行文件的功能,它似乎可以工作:
[tjm@ArchPad poc]$ ./poc
abcd
关于为什么会发生这种情况,以及如何防止GCC助长上述错误,有什么想法吗?
立即数移动指令movq $S, %rsi
尽管使用64位寄存器,但只使用32位带符号的立即数。当构建要使用ASLR运行的与位置无关的可执行文件时,就像大多数Linux系统上的默认设置一样,您的程序通常不位于虚拟内存的低或高31位中,因此像S
这样的全局或静态对象的地址不适合带符号的32位立即数。
一个修复方法是使用movabs
,它确实需要一个完整的64位立即数。用movabs %0, %%rsi
替换movq %0, %%rsi
可以构建代码。然而,更好的方法是使用RIP相对寻址lea S(%rip), %rsi
,这是一条较短的指令,并且避免了在加载时重新定位的需要。在内联asm中这样做有点尴尬,所以您可以让编译器使用"S" (S)
这样的输入操作数将地址加载到寄存器中(令人困惑的是,rsi
寄存器的约束是S
,它恰好与您为数组变量选择的名称一致(。
当您手动链接时,您构建了一个非位置独立的可执行文件,这意味着将地址视为常量是有效的。将-no-pie
传递到gcc
也可以获得相同的效果。
您的代码还有另一个严重的问题,因为它没有声明它撞击的寄存器——不仅是那些您显式mov
的寄存器,还有syscall
隐式修改的rcx
和r11
。您应该看看如何在内联程序集中通过syscall或sysenter调用系统调用?其具有正确的示例。仔细研究这些例子是明智的——GCC内联asm功能强大,但也很容易以编译器无法帮助您检测的微妙方式出错,而且可能在简单的程序中工作,但在更复杂的程序中不可预测地失败。
GCC支持ASM指令的多种方言,默认值可能有所不同。点击此处阅读更多:
https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html#Extended-Asm
当然,不同的方言是不兼容的。