第66章 系统调用(syscall-s)

我们都知道,所有在操作系统中运行的进程可以分成两类:一类进程对具有硬件的全部访问权限,运行于内核空间(kernel space);另一类进程不能直接访问硬件地址,运行于用户空间(user space)。

操作系统的内核以及常规的驱动程序都运行于内核空间。普通的应用程序通常运行于用户空间。

具体来说,Linux的内核运行于空间;而Glibc(底层API)则运行于空间。

这两种空间的隔离措施对于操作系统的安全性至关重要。若没有隔离措施,所有程序都可以干扰其他进程甚至破坏操作系统的内核。另一方面来看,即使实现了两种空间的隔离措施,一旦运行于内核空间的驱动程序发生错误、或者是操作系统的内核组件存在问题,整个内核照样会崩溃甚至发生BSOD(Black Screen of Death)的“蓝天白云”故障。

虽然x86 CPU引入了特权等级的概念,将进程权限分为ring0~ring3,但是Linux和Windows只使用了其中的2个级别控制进程权限:ring0(内核空间)和ring3(用户空间)。

由操作系统提供的系统调用(syscall)构成了ring0和ring 3之间的访问机制。可以说,系统调用就是操作系统为应用程序提供的应用编程接口API。

而在Windows NT环境下,系统调用表位于系统服务分配表SSDT(System Service Dispatch Table)。

计算机病毒以及shellcode大多都会利用系统调用。这是因为系统库函数的寻址过程十分麻烦,而直接调用系统调用却相对简单。虽然系统调用的访问过程并不麻烦,但是由于系统调用本身属于底层API,因此直接使用系统调用的程序也不好写。另外需要注意的是:系统调用的总数由操作系统和系统版本两个因素共同决定的。

66.1 Linux

Linux程序通常通过80号中断/INT 80调用系统调用。在调用系统调用时,程序应当通过EAX寄存器指定被调用函数的编号,再使用其他寄存器声明系统调用的参数。

指令清单66.1 使用两次系统调用syscall的简单例子

section .text
global _start

_start:
        mov     edx,len ; buffer len
        mov     ecx,msg ; buffer
        mov     ebx,1   ; file descriptor. 1 is for stdout
        mov     eax,4   ; syscall number. 4 is for sys_write
        int     0x80

        mov     eax,1   ; syscall number. 4 is for sys_exit
        int     0x80

section .data

msg     db  'Hello, world!',0xa
len     equ $ - msg

编译指令如下所示。

nasm -f elf32 1.s
ld 1.o

完整的Linux系统调用列表可以参考:http://go.yurichev.com/17319

如需截获或追踪Linux系统调用的访问过程,可使用本书71章介绍的strace程序。

66.2 Windows

Windows程序可通过0x2e号中断/int 0x2e、或x86专用指令SYSENTER访问系统调用。

这里使用的中断数是0x2e,或者采用x86下的特殊指令SYSENTER。

完整的Windows系统调用列表可以参考:http://go.yurichev.com/17320

进一步的阅读,可以参阅Piotr Bania编写的Windows Syscall Shellcode一书:http://go.yurichev. com/17321