我们都知道,所有在操作系统中运行的进程可以分成两类:一类进程对具有硬件的全部访问权限,运行于内核空间(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,因此直接使用系统调用的程序也不好写。另外需要注意的是:系统调用的总数由操作系统和系统版本两个因素共同决定的。
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程序。
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
。