Linux下如何使用sysenter?

逆向工程 部件 x86
2021-07-02 02:03:56

我想知道在 Linux 下在 x86 汇编器中执行系统调用的不同方法是什么。但是,没有作弊,只能使用汇编程序(即编译gcc必须使用 -nostdlib)。

我知道四种执行系统调用的方法,即:

  • int $0x80
  • sysenter (i586)
  • call *%gs:0x10 (vdso 蹦床)
  • syscall (amd64)

我很擅长使用int $0x80,例如,这里有一个经典的“Hello World!”的示例代码。在汇编程序中使用int $0x80(用 编译gcc -nostdlib -o hello-int80 hello-int80.s):

.data
msg:
  .ascii "Hello World!\n"
  len = . - msg

.text
.globl _start

_start:
# Write the string to stdout
  movl  $len, %edx
  movl  $msg, %ecx
  movl  $1, %ebx
  movl  $4, %eax
  int   $0x80

# and exit
  movl  $0, %ebx
  movl  $1, %eax
  int   $0x80

sysenter通常以分段错误错误结束。为什么 ?而且,如何正确使用它?

这是一个带有call *%gs:0x10(用 编译gcc -o hello-gs10 hello-gs10.s的示例请注意,libc在正确调用它之前,我需要进行初始化(这就是我使用main而不是再使用的原因_start,这也是我-nostdlib从编译行中删除该选项的原因):

.data
msg:
  .ascii "Hello World!\n"
  len = . - msg

.text
.globl main

main:
# Write the string to stdout
  movl  $len, %edx
  movl  $msg, %ecx
  movl  $1, %ebx
  movl  $4, %eax
  call  *%gs:0x10

# and exit
  movl  $0, %ebx
  movl  $1, %eax
  call  *%gs:0x10

此外,syscall如果您知道此架构系统调用代码(感谢 lfxgroove)(编译为:),它也能正常工作gcc -m64 -nostdlib -o hello-syscall hello-syscall.s

.data
msg:
  .ascii "Hello World!\n"
  len = . - msg

.text
.globl _start

_start:
# Write the string to stdout
  movq  $len, %rdx
  movq  $msg, %rsi
  movq  $1, %rdi
  movq  $1, %rax
  syscall
# and exit
  movq  $0, %rdi
  movq  $60, %rax
  syscall

因此,我必须以这种sysenter方式触发系统调用的唯一问题这是一个sysenter以分段错误结尾的示例(编译为gcc -m32 -nostdlib -o hello-sysenter hello-sysenter.s):

.data
msg:
  .ascii "Hello World!\n"
  len = . - msg

.text
.globl _start

_start:
# Write the string to stdout
  movl  $len, %edx
  movl  $msg, %ecx
  movl  $1, %ebx
  movl  $4, %eax

  push    final
  sub $12, %esp
  mov %esp, %ebp

  sysenter
# and exit
final:  
  movl  $0, %ebx
  movl  $1, %eax

  sub $12, %esp
  mov %esp, %ebp

  sysenter
1个回答

系统调用通过 sysenter

sysenter是一条 i586 指令,特别适用于 32 位应用程序。它已包含syscall在 64 位平台上。

的一个特殊性sysenter是,除了通常的寄存器设置之外,在调用它之前确实需要对堆栈进行一些操作。这是因为在离开之前sysenter,该过程将经过__kernel_vsyscall汇编代码段的最后部分(从 开始0xf7ffd430):

Dump of assembler code for function __kernel_vsyscall:
   0xf7ffd420 <+0>:        push   %ecx
   0xf7ffd421 <+1>:        push   %edx
   0xf7ffd422 <+2>:        push   %ebp
   0xf7ffd423 <+3>:        mov    %esp,%ebp
   0xf7ffd425 <+5>:        sysenter 
   0xf7ffd427 <+7>:        nop
   0xf7ffd428 <+8>:        nop
   0xf7ffd429 <+9>:        nop
   0xf7ffd42a <+10>:       nop
   0xf7ffd42b <+11>:       nop
   0xf7ffd42c <+12>:       nop
   0xf7ffd42d <+13>:       nop
   0xf7ffd42e <+14>:       int    $0x80
=> 0xf7ffd430 <+16>:       pop    %ebp
   0xf7ffd431 <+17>:       pop    %edx
   0xf7ffd432 <+18>:       pop    %ecx
   0xf7ffd433 <+19>:       ret    
End of assembler dump.

因此,该sysenter指令期望以这种方式伪造堆栈:

0x______0c  saved_eip   (ret)
0x______08  saved_%ecx  (pop %ecx)
0x______04  saved_%edx  (pop %edx)
0x______00  saved_%ebp  (pop %ebp)

这就是为什么,每次我们需要调用时sysenter,我们首先要推动保存的值%eip,并以相同的%ecx%edx%ebp这导致:

.data
msg:
    .ascii "Hello World!\n"
    len = . - msg

.text
.globl _start
_start:
    pushl  %ebp
    movl   %esp, %ebp
# Write the string to stdout
    movl   $len, %edx
    movl   $msg, %ecx
    movl   $1, %ebx
    movl   $4, %eax
# Setting the stack for the systenter
    pushl  $sysenter_ret
    pushl  %ecx
    pushl  %edx
    pushl  %ebp
    movl   %esp,%ebp
    sysenter
# and exit
sysenter_ret:    
    movl   $0, %ebx
    movl   $1, %eax
# Setting the stack for the systenter
    pushl  $sysenter_ret # Who cares, this is an exit !
    pushl  %ecx
    pushl  %edx
    pushl  %ebp
    movl   %esp,%ebp
    sysenter