1 概述
vsyscall和vDSO是用于加速某些系统调用的两种机制。
传统的int 0x80有点慢, Intel和AMD分别实现了sysenter/sysexit和syscall/ sysret, 即所谓的快速系统调用指令, 使用它们更快, 但是也带来了兼容性的问题. 于是Linux实现了vsyscall, 程序统一调用vsyscall, 具体的选择由内核来决定. 而vsyscall的实现就在VDSO中。
【vsyscall】
用来执行特定的系统调用,减少系统调用的开销。某些系统调用并不会向内核提交参数,而仅仅只是从内核里请求读取某个数据,例如gettimeofday(),内核在处理这部分系统调用时可以把系统当前时间写在一个固定的位置(由内核在每个时间中断里去完成这个更新动作),mmap映射到用户空间。这样会更快速,避免了传统系统调用模式INT 0x80/SYSCALL造成的内核空间和用户空间的上下文切换。
【vsyscall的局限】
分配的内存较小;
只允许4个系统调用;
Vsyscall页面在每个进程中是静态分配了相同的地址;
【vDSO】
提供和vsyscall相同的功能,同时解决了其局限。
vDSO是动态分配的,地址是随机的;
可以提供超过4个系统调用;
vDSO是glibc库提供的功能;
2 X64
测试环境Ubuntu 16.04
2.1 查看vsyscall/VDSO内存映射
millionsky@ubuntu-16:~/tmp/VDSO$ cat /proc/`pgrep cat | head -1`/maps | egrep 'vdso|vsyscall'
7ffdb57da000-7ffdb57dc000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
2.2 导出VDSO/vsyscall
2.2.1 Gdb导出vdso/vsyscall
millionsky@ubuntu-16:~/tmp/VDSO$ gdb -q /bin/ls Reading symbols from /bin/ls...(no debugging symbols found)...done. (gdb) tb __open Function "__open" not defined. Make breakpoint pending on future shared library load? (y or [n]) y Temporary breakpoint 1 (__open) pending. (gdb) r Starting program: /bin/ls
Temporary breakpoint 1, open64 () at ../sysdeps/unix/syscall-template.S:84 84 ../sysdeps/unix/syscall-template.S: 没有那个文件或目录. (gdb) info program Using the running image of child process 12012. Program stopped at 0x7ffff7df2320. It stopped at a breakpoint that has since been deleted. Type "info stack" or "info registers" for more information. (gdb) shell cat /proc/12012/maps | grep vdso 7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso] (gdb) dump memory /tmp/linux-vdso.so.1 0x7ffff7ffa000 0x7ffff7ffc000 (gdb) q |
同样的方法可以导出vsyscall
2.2.2 python导出vdso
这里是Python调用的C库函数
2.2.3 C导出vdso
2.3 VDSO内容
VDSO中包含以下4个系统调用:
/usr/include/x86_64-linux-gnu/asm/unistd_64.h
#define __NR_gettimeofday 96 //0x60
#define __NR_time 201 //0xc9
#define __NR_clock_gettime 228 //0xE4
#define __NR_getcpu 309 //0x135
IDA查看
objdump查看
millionsky@ubuntu-16:~/tmp/VDSO$ objdump -d /tmp/linux-vdso.so.1 | grep -A5 ">:" 0000000000000930 <__vdso_clock_gettime@@LINUX_2.6-0xc0>: 930: 55 push %rbp 931: 48 89 e5 mov %rsp,%rbp 934: 0f ae e8 lfence 937: 0f 31 rdtsc 939: 48 c1 e2 20 shl $0x20,%rdx -- 00000000000009f0 <__vdso_clock_gettime@@LINUX_2.6>: 9f0: 55 push %rbp 9f1: 41 89 f9 mov %edi,%r9d 9f4: 48 89 e5 mov %rsp,%rbp 9f7: 53 push %rbx 9f8: 48 8d 5d f4 lea -0xc(%rbp),%rbx -- 0000000000000c30 <__vdso_gettimeofday@@LINUX_2.6>: c30: 55 push %rbp c31: 48 89 e5 mov %rsp,%rbp c34: 53 push %rbx c35: 48 83 ec 10 sub $0x10,%rsp c39: 48 85 ff test %rdi,%rdi -- 0000000000000d80 <__vdso_time@@LINUX_2.6>: d80: 55 push %rbp d81: 48 85 ff test %rdi,%rdi d84: 48 8b 05 1d d3 ff ff mov -0x2ce3(%rip),%rax # ffffffffffffe0a8 <__vdso_getcpu@@LINUX_2.6+0xffffffffffffd308> d8b: 48 89 e5 mov %rsp,%rbp d8e: 74 03 je d93 <__vdso_time@@LINUX_2.6+0x13> -- 0000000000000da0 <__vdso_getcpu@@LINUX_2.6>: da0: 55 push %rbp da1: b8 7b 00 00 00 mov $0x7b,%eax da6: 48 89 e5 mov %rsp,%rbp da9: 0f 03 c0 lsl %ax,%eax dac: 90 nop |
2.4 Vsyscall内容
vsyscall中包含3个系统调用:
/usr/include/x86_64-linux-gnu/asm/unistd_64.h
#define __NR_gettimeofday 96 //0x60
#define __NR_time 201 //0xc9
#define __NR_clock_gettime 228//0xE4
#define __NR_getcpu 309 //0x135
地址是固定的:
基地址:0xffffffffff600000
#define VSYSCALL_ADDR_vgettimeofday 0xffffffffff600000
#define VSYSCALL_ADDR_vtime 0xffffffffff600400
#define VSYSCALL_ADDR_vgetcpu 0xffffffffff600800
IDA查看内容
Objdump查看内容
millionsky@ubuntu-16:~/tmp/VDSO$ objdump -D -b binary -mi386:x86-64 /tmp/linux-vsyscall.so | grep syscall -C 2
/tmp/linux-vsyscall.so: 文件格式 binary
-- 0000000000000000 <.data>: 0: 48 c7 c0 60 00 00 00 mov $0x60,%rax 7: 0f 05 syscall 9: c3 retq a: cc int3 -- 3ff: cc int3 400: 48 c7 c0 c9 00 00 00 mov $0xc9,%rax 407: 0f 05 syscall 409: c3 retq 40a: cc int3 -- 7ff: cc int3 800: 48 c7 c0 35 01 00 00 mov $0x135,%rax 807: 0f 05 syscall 809: c3 retq 80a: cc int3 |
3 X86
测试环境Ubuntu 12.04
linux-vdso.so.1
老点的版本是linux-gate.so.1
3.1 查看VDSO内存映射
millionsky@ubuntu-12:~/tmp$ ldd /bin/cat linux-gate.so.1 => (0xb776a000) libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb75ad000) /lib/ld-linux.so.2 (0xb776b000) millionsky@ubuntu-12:~/tmp$ cat /proc/self/maps | grep vdso b77c1000-b77c2000 r-xp 00000000 00:00 0 [vdso] |
3.2 导出VDSO
见X64章节
3.3 VDSO内容
Objdump查看
millionsky@ubuntu-12:~/tmp$ objdump -d linux-gate.so.1
linux-gate.so.1: file format elf32-i386
Disassembly of section .text:
ffffe400 <__kernel_sigreturn>: ffffe400: 58 pop %eax ffffe401: b8 77 00 00 00 mov $0x77,%eax ffffe406: cd 80 int $0x80 ffffe408: 90 nop ffffe409: 8d 76 00 lea 0x0(%esi),%esi
ffffe40c <__kernel_rt_sigreturn>: ffffe40c: b8 ad 00 00 00 mov $0xad,%eax ffffe411: cd 80 int $0x80 ffffe413: 90 nop
ffffe414 <__kernel_vsyscall>: ffffe414: 51 push %ecx ffffe415: 52 push %edx ffffe416: 55 push %ebp ffffe417: 89 e5 mov %esp,%ebp ffffe419: 0f 34 sysenter ffffe41b: 90 nop ffffe41c: 90 nop ffffe41d: 90 nop ffffe41e: 90 nop ffffe41f: 90 nop ffffe420: 90 nop ffffe421: 90 nop ffffe422: cd 80 int $0x80 ffffe424: 5d pop %ebp ffffe425: 5a pop %edx ffffe426: 59 pop %ecx ffffe427: c3 ret |
4 附加向量
1. 附加向量位于环境指针数组的后面,环境字符串的前面;
2. 附加向量中的AT_SYSINFO_EHDR指示VDSO的地址;
3. 附加向量中的AT_SYSINFO指示__kernel_vsyscall的地址(32位系统);
4. 附加向量中的AT_RANDOM指示栈中16字节随机数的地址。在栈中位于附加向量的后面,环境字符串的前面。可以通过这个地址计算栈中数据的地址。
5. AT_PLATFORM指示平台字符串,可以通过这个地址计算栈中数据的地址。
5 VDSO的爆破
【32位】
https://www.anquanke.com/post/id/85810
Sigreturn Oriented Programming攻击简介
这篇文章描述了32位vdso的爆破
32位的VDSO只有1个字节是随机的
vdso_range = range(0xf7700000, 0xf7800000, 0x1000)
在Ubuntu 16.04 64上测试32位程序失败(已经调整了/bin/sh,bss,sigreturn_offset,syscall_offset)
l 失败的原因在于main函数的栈溢出不好搞。Main开始对ESP进行了取整,EBP-4的地方存储的是addr_of_argc。Main的结尾,ESP是通过EBP-4处保存的addr_of_argc进行恢复的;
l 将漏洞代码放入单独的函数中时,爆破成功
[+] Starting local process './srop_test': pid 25791 vdso_addr: 0xf777b000 [*] Process './srop_test' stopped with exit code -11 (SIGSEGV) (pid 25791) nTry 169 [+] Starting local process './srop_test': pid 25794 vdso_addr: 0xf77a7000 [*] Switching to interactive mode
$ whoami millionsky $ ls core makefile srop3.py srop3_single_test.py srop_test srop_test.c $ exit [*] Got EOF while reading in interactive |
【64位】
但是对x64来说,爆破vdso就比较难了。原来只有11bit是随记的,但我在我的linux上测试好像有22位是随机的了,爆破也就几小时而已(个人亲测),还是能爆出来的。关于64位的爆破,可参考Return to VDSO using ELF Auxiliary Vectors。
尚未测试
6 参考文章
1. On vsyscalls and the vDSO。https://lwn.net/Articles/446528/。
2. 64位Linux下的系统调用。http://www.lenky.info/archives/2013/02/2199。
3. vsyscalls and vDSO。https://0xax.gitbooks.io/linux-insides/content/SysCall/syscall-3.html。
4. Creating a vDSO: the Colonel's Other Chicken。https://www.linuxjournal.com/content/creating-vdso-colonels-other-chicken?page=0,2。
5. Return to VDSO using ELF Auxiliary Vectors。https://v0ids3curity.blogspot.jp/2014/12/return-to-vdso-using-elf-auxiliary.html。
6.Sigreturn Oriented Programming攻击简介。https://www.anquanke.com/post/id/85810