以下代码使用rsi寄存器作为循环计数器打印hello world 10次。
section .data
hello: db 'Hello world!',10
helloLen: equ $-hello
section .text
global _start
_start:
mov rsi, 0 ;<--- use r8 here
do_loop:
inc rsi ;<--- use r8 here
;print hello world
mov eax,4
mov ebx,1
mov ecx,hello
mov edx,helloLen
int 80h
cmp rsi, 10 ;<--- use r8 here
jnz do_loop
;system exit
mov eax,1 ; The system call for exit (sys_exit)
mov ebx,0 ; Exit with return code of 0 (no error)
int 80h;
如果我尝试使用r8寄存器而不是rsi作为循环计数器,则会导致无限循环。这里的r8寄存器只是一个例子。它也适用于寄存器r9,r10。
有人可以解释一下,因为我认为这些都是通用寄存器,你应该被允许使用它们吗?
答案 0 :(得分:6)
TL; DR :int 0x80
隐含零 R8 , R9 <返回用户态代码之前,64位Linux系统上的/ em>, R10 和 R11 。在2.6.32-rc1之后的内核上会发生此行为。对于首选的64位 SYSCALL 调用约定,情况并非如此。
在2.6.32-rc1版本之后,您正在体验Linux内核的特性。对于Linux内核版本&lt; = 2.6.32-rc1,您可能会得到您期望的行为。由于信息泄漏错误(和漏洞利用),寄存器 R8 , R9 , R10 和 R11 现在当内核从int 0x80
返回时归零。
您可能认为这些寄存器在兼容模式(32位代码)中不重要,因为那些较新的寄存器不可用。这是一个错误的假设,因为32位应用程序可以切换到64位长模式并访问这些寄存器。确定此问题的Linux Kernel Mailing List post有这样的说法:
x86:不要将64位内核寄存器值泄漏到32位进程
虽然32位进程无法直接访问R8 ... R15,但它们可以获得 通过临时切换自己来访问这些寄存器 64位模式。
Jon Oberheide提供了演示早期内核上的寄存器泄漏的代码。它创建了一个32位应用程序,可在x86-64系统上运行,并启用IA32兼容性。程序切换到64位长模式,然后将寄存器 R8 - R11 存储到通用寄存器中,这些寄存器在兼容模式(32位模式)下可用。 John讨论了这个article中的细节。他在这段摘录中总结了漏洞和内核修复:
漏洞
导致此漏洞的根本问题是缺乏 从a返回时将几个x86-64寄存器归零 系统调用。 32位应用程序可能能够切换到64位模式 并访问 r8,r9,r10和r11 寄存器以泄漏其先前的 值。这个问题是由Jan Beulich发现并修补的 10月1日。 修复显然是将这些寄存器清零 避免将任何信息泄露给用户空间。
如果您在 GDB 之类的调试器中单步执行代码,您应该会发现 R8 实际上在int 0x80
之后设置为零。由于它是你的循环计数器,你的程序最终会以无限循环打印Hello world!
。