Vsyscall系统调用

前言

我们知道,在开启了ASLR的系统上运行PIE程序,就意味着所有的地址都是随机化的。然而在某些版本的系统中这个结论并不成立,原因是存在着一个神奇的vsyscall。但是vsyscall仅在部分linux发行版本中可用,如Ubuntu16.04。vsyscall在内核中实现,无法用docker模拟,因此任何与vsyscall相关的实验都改成在Ubuntu 16.04进行。

Vsyscall

由于一般的系统调用如果想要向内核传递一些参数的话,为了保证用户态和内核态的数据隔离,往往需要把当前寄存器状态先保存好,然后切换到内核状态,当执行完后还需要恢复寄存器的状态,而这中间就会产生大量的系统开销。因此为了解决这个问题,linux系统会将仅从内核里面读取数据的syscall单独列出来进行优化,如gettimeofday,time,getcpu。而其地址也将是固定的,原型如下:

1
#define VSYSCALL_ADDR (-1UL << 20)

通过这段代码可以确定这部分是固定的,也就是ffffffffff600000

而如果将Vsyscall的内存页dump出来的话(在gdb的时候 dump memory ./dump 首地址 尾地址) ,会发现全是通过syscall系统调用来执行的。但是又与普通的syscall系统调用不同,该段代码会在开头验证检查,如果不是从函数开头执行的话就会出错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
__vsyscall_page:
mov $__NR_gettimeofday, %rax
syscall
ret

.balign 1024, 0xcc
mov $__NR_time, %rax
syscall
ret

.balign 1024, 0xcc
mov $__NR_getcpu, %rax
syscall
Ret

而这三个系统调用的地址如下

1
2
3
#define VSYSCALL_ADDR_vgettimeofday  0xffffffffff600000
#define VSYSCALL_ADDR_vtime 0xffffffffff600400
#define VSYSCALL_ADDR_vgetcpu 0xffffffffff600800

我们可以通过cat /proc/self/maps来查看当前进程的内存映射,然后可以发现vsyscall的地址始终在 0xffffffffff600000 ~ 0xffffffffff601000之间。

vdso

vsyscall 已经被vdso替代。

正如我在上面已经写过的,vsyscall是一个过时的概念,由vDSO(virtual dynamic shared object)取代。vsyscallvDSO机制的主要区别在于,vDSO内存页面以共享对象的形式映射到每个进程中,但vsyscall在内存中是静态的,并且每次都具有相同的地址。对于x86_64架构,它被称为 - linux-vdso.so.1。所有用户空间应用程序都通过glibc

1
2
3
4
root@74f21669baa4:/ctf/work/competition/2022xiangyuncup/unexp # ldd unexploitable
linux-vdso.so.1 (0x00007ffff7fcd000)
./libc-2.27_debug.so (0x00007ffff7800000)
/glibc/2.27/64/lib/ld-linux-x86-64.so.2 => /lib64/ld-linux-x86-64.so.2 (0x00007ffff7fcf000)

或者:

1
2
root@74f21669baa4:/ctf/work/competition/2022xiangyuncup/unexp # cat /proc/1/maps  | grep vdso   
7fff091f5000-7fff091f7000 r-xp 00000000 00:00 0 [vdso]

总结

其实说到底来说vsyscall就是syscall的一些只需要读数据的系统调用的封装,是用来加速系统调用的处理的。

由于Vsyscall地址的固定性,这个本来是为了节省开销的设置造成了很大的隐患,因此Vsyscall很快就被新的机制vdso所取代。与Vsyscall不同的是,vdso的地址也是随机化的,且其中的指令可以任意执行,不需要从入口开始。

vsyscall的局限

  • 分配的内存比较小
  • 只允许四个系统调用
  • Vsyscall页面在每个进程中是静态分配了相同的地址;

vdso

  • 提供和Vsyscall相同的功能,同时解决了其局限性
  • vdso是动态分配的,地址是随机的;
  • 可以提供超过4个系统调用;
  • vdso是glibc库提供的功能