Vsyscall系统调用
Vsyscall系统调用
前言
我们知道,在开启了ASLR的系统上运行PIE程序,就意味着所有的地址都是随机化的。然而在某些版本的系统中这个结论并不成立,原因是存在着一个神奇的vsyscall。但是vsyscall仅在部分linux发行版本中可用,如Ubuntu16.04。
vsyscall在内核中实现,无法用docker模拟,因此任何与vsyscall相关的实验都改成在Ubuntu 16.04进行。
Vsyscall
由于一般的系统调用如果想要向内核传递一些参数的话,为了保证用户态和内核态的数据隔离,往往需要把当前寄存器状态先保存好,然后切换到内核状态,当执行完后还需要恢复寄存器的状态,而这中间就会产生大量的系统开销。因此为了解决这个问题,linux系统会将仅从内核里面读取数据的syscall单独列出来进行优化,如gettimeofday,time,getcpu。而其地址也将是固定的,原型如下:
1 |
通过这段代码可以确定这部分是固定的,也就是ffffffffff600000
。
而如果将Vsyscall的内存页dump出来的话(在gdb的时候 dump memory ./dump 首地址 尾地址
) ,会发现全是通过syscall系统调用来执行的。但是又与普通的syscall系统调用不同,该段代码会在开头验证检查,如果不是从函数开头执行的话就会出错。
1 | __vsyscall_page: |
而这三个系统调用的地址如下
1 |
我们可以通过cat /proc/self/maps
来查看当前进程的内存映射,然后可以发现vsyscall的地址始终在 0xffffffffff600000 ~ 0xffffffffff601000
之间。
vdso
vsyscall 已经被vdso替代。
正如我在上面已经写过的,vsyscall
是一个过时的概念,由vDSO
(virtual dynamic shared object)取代。vsyscall
和vDSO
机制的主要区别在于,vDSO
内存页面以共享对象的形式映射到每个进程中,但vsyscall
在内存中是静态的,并且每次都具有相同的地址。对于x86_64
架构,它被称为 - linux-vdso.so.1
。所有用户空间应用程序都通过glibc
1 | root@74f21669baa4:/ctf/work/competition/2022xiangyuncup/unexp # ldd unexploitable |
或者:
1 | root@74f21669baa4:/ctf/work/competition/2022xiangyuncup/unexp # cat /proc/1/maps | grep vdso |
总结
其实说到底来说vsyscall就是syscall的一些只需要读数据的系统调用的封装,是用来加速系统调用的处理的。
由于Vsyscall
地址的固定性,这个本来是为了节省开销的设置造成了很大的隐患,因此Vsyscall
很快就被新的机制vdso
所取代。与Vsyscall不同的是,vdso的地址也是随机化的,且其中的指令可以任意执行,不需要从入口开始。
vsyscall的局限
- 分配的内存比较小
- 只允许四个系统调用
- Vsyscall页面在每个进程中是静态分配了相同的地址;
vdso
- 提供和Vsyscall相同的功能,同时解决了其局限性
- vdso是动态分配的,地址是随机的;
- 可以提供超过4个系统调用;
- vdso是glibc库提供的功能