首页 > 其他分享 >虚拟文件系统的实现思路

虚拟文件系统的实现思路

时间:2023-09-29 16:45:55浏览次数:45  
标签:文件 虚拟 文件名 CreateFile 文件系统 Hook API 思路 VFS

虚拟文件系统的实现思路

VFS (Virtual File System) (虚拟文件系统)
这里讨论的VFS,是区别于系统中的VFS,更多的是指代自己实现的小型简易的文件系统。
像是常见的游戏封包,也可以作为一种VFS的数据结构部分。
全部情况都基于Windows平台进行讨论。

VFS 的架构概念

接口部分

需要把VFS应用在何处,就决定了接口的设计。

接口的本质问题,就是获取需要替换或读取的文件信息,并把处理好后的信息正确返回,同时响应读写操作。

比如需要用在任意程序上,那么接口部分应该针对系统API进行设计
如果单纯是用在某个游戏的引擎上,那么接口部分应该根据这个引擎的文件读取接口来设计
又或者,只想用在自己正常的项目内部,那么就可以舍弃掉一些针对系统API的设计

当然你也可以先定义公共的接口,在此基础上,为不同使用场景具体优化。

访问部分

这部分我们已经获得需要操作的文件信息,比如要打开某个文件,要读取某个文件,要移动文件指针。

这部分的目标就是正确的访问VFS自己维护的封包中的数据,和响应接口部分需要的操作。

首先就是应该正确的进行查找以确定该文件VFS中确实有,或者传入一个标识,要能确定该标识确实是VFS产生的并且在内部维护的,而不是系统的。为了保证标识的唯一性,应该采取何种方法。

比如对路径的处理,对文件名的处理,对文件指针的处理,对文件句柄的处理

像是路径有反斜杠,斜杠,相对路径,绝对路径的问题。文件名有大小写,匹配速度等问题,文件指针和句柄有是否与正常的文件读写冲突的问题,和文件关闭的时候处理。

接着就是读取的问题,这里我们就不讨论写入问题了,一般这种VFS没这样的需求。

这的复杂度,主要和你想实现的功能有关,比如有加密,我们应该在写入目标buffer之前正确的解密,压缩文件也是类似的要正确的解压,当然也需要考虑性能问题,还有小文件的优化,缓冲区之类的问题。

结构部分

结构这块实际上就和游戏的封包差不多了,当然这个结构也包括运行时的结构和VFS整个文件数据在硬盘上的结构,怎样去设计一个灵活又高效的结构,也是一个比较难的问题。

对于硬盘上的结构:

像是采用传统的文件头+索引头+数据段,这种如果要追加数据是不怎么方便的。

或者可以考虑把文件头和索引头放在末尾,即 数据段 + 索引头 + 文件头,这样追加文件只需要修改索引头和文件头即可,不需要整个文件都重写。

也有采用块结构的,类似PNG图片那种。

还有内部路径的问题,是保存每个文件的完整路径,还是分文件夹?

文件名的问题,是Hash,是定长,还是变长?

对于运行时的结构:

这部分主要是关乎到整个文件系统的运行效率和使用的方便与否。

VFS 目的和作用

很多的汉化补丁用到VFS,有些是自己编写的针对具体引擎的,有些则是一些商业软件通用的VFS,正如其名一样,VFS的目的是构建一个自己的文件系统来让游戏或程序读取文件。

一个经典的例子是,我们希望一个补丁,可以叠加在原版的游戏上,而不是影响原版游戏的正常运行,也就是希望汉化和日文版都可以正常运行,这时候可能有些文件需要替换,但是文件名重复了,大可以修改程序里的文件名,但是有了VFS,我们可以把文件封装到VFS的封包中,实现相同文件名的替换,从而实现补丁的共存。

当然如果数据封装到VFS自己的封包中,也就可以实现一些加密,压缩等功能。

还有个例子就是,很多游戏引擎都支持免封包功能,但是免封包带来的问题就是文件可能会非常多,或者有些同名的文件会替换掉原始的文件,如果采用VFS,就可以把全部零散的文件,打包到一起,从而避免了文件散落一地,频繁调用IO接口,影响性能,文件名重复等问题。

当然,如果你并不在乎文件数量和加密的问题,大可以把路径重定向,依然可以解决问题。

VFS的目的更多的是为了加密和打包而存在。

那么顺着这个话题继续往下。

游戏读取封包中的数据算不算一种VFS呢?

VFS 接口核心思路

在Windows下,打开文件是调用CreateFile这个系统API,即使是std::fstream,fopen,也只是对CreateFile的封装,这里揭露了一个事实,即很多标准库的函数,其实都是对系统API的封装。因为有一些操作,并不是单纯从算法上实现,而是需要依赖系统所提供的功能,比如硬件通信。

那么这里我们就需要回顾一下,文件的操作。
由于库函数对文件的操作也绕不开系统API,这里就不讨论,其实是大同小异的。

读取一个文件首先需要打开文件,就是调用CreateFile,向文件写入数据也一样,CreateFile的返回值如果成功,则是一个句柄,该句柄只是个标识,用于唯一标识这个文件,并用于其它文件操作函数的参数。在C语言下和文件指针类似,但是CreateFile返回的是一个内核对象的标识,句柄并不具备实际意义,只是个编号而已。如果这个时候我想读取文件,那么我应该给ReadFile指明是读取哪个文件,这个指明哪一个文件就是用句柄,WriteFile,GetFileSize,SetFilePointer,都需要文件句柄,这很好理解,不然你怎么知道你要操作的是哪个文件。从这我们就不难看出,操作文件的核心,就是在CrateFile之后返回的那个句柄。

所以核心的思路就是抓住句柄,处理好句柄,确保非VFS的句柄正确被传递给系统,确保VFS内部维护的句柄被正确处理,正确释放。

VFS 接口实现方向

自己开发这样一个简单的VFS是可行的

我们实现VFS的目的本质上就是去管理文件,更一般的,其实只是替换文件而已。

Hook 系统API

我们只需要在游戏打开文件的过程中,对需要的替换的文件进行筛选过滤,并接管其对该文件的操作,由于正常的程序,操作文件都会经过系统的API,所以我们只需要Hook系统的API就可以达到控制游戏的文件读写操作。

如果要通用性的话,无非就是Hook相关的API,像是CreateFile,WriteFile, ReadFile,CloseHandle,GetFileSize,GetFileAttributes,等,这些都是库函数最终要经过的API,换句话说,库函数也只是不过是对这些API的封装,再说的明白点,一般情况下Windows下操作文件都和这些API相关,不管你是何种程序。那么如果还需要再深入一些的话,事实上我们应该Hook的是从用户层到内核层的那个函数,像是NtCreateFile,说简单一点,其实CreateFile也只不过是对别的函数的封装,只不过在用户层上,我们最多就到NtCreateFile了,因为这个函数马上就调用了系统call进入了内核。一般程序是没有内核的范围权限的,正常开发也不需要直接操作内核。其它文件操作函数也是类似的,你只要Hook到进入内核层之前的部分,就可以把所有在用户层封装这个函数的地方都截获,当然,是否要这样处理,取决于你的VFS要做到何种功能。

Hook 程序内部接口

如果不想Hook系统的API,也可以选择Hook游戏内部读取文件的相关接口,因为游戏引擎一般不会每次打开文件都去CreateFile,fopen,fstream,而是内部封包好一个公共的接口来使用,那么这个接口和CreateFile这些又有什么差异呢?也只不过是多了些自己的功能和处理罢了。

正常接入项目

还有种情况是你想用在自己已有的项目上,那么依据你自己的程序逻辑正常接入就好了。

VFS 数据结构

没啥好写的,各有差异,具体可以参考那些游戏的封包结构,各种各样的都有。

内部结构,看你自己怎么处理,方法各异。

VFS 运行流程

举个例子,CreateFile的目的是打开一个文件,就像是fopen一样,获得一个对该文件对象的标识(CreateFile里叫文件句柄,fopen则是文件指针),后需要操作这个文件都需要这个标识,如果CreateFile一个不存在的问,自然是没办法返回这个标识的,但是我们可以Hook,CreateFile,发现它打开的文件虽然真正意义上的磁盘上并不存在该文件,但是我们可以假装返回一个我们自己内部维护的标识,让它以为成功了,然后ReadFile的时候它会把这个标识传进来,因为需要指定读取的是那一个已经打开的文件内容,Hook ReadFile,这时候我就可以捕获这个我们自己维护的标识,从而,不让ReadFile去操作系统里找这个根本就不实际存在的标识,然后我们可以自己在内部打开我们的自己的封包,或者别的数据结构之类的,找到这个具体的文件文件,并把数据读取到ReadFile传进来的Buffer里面。对于游戏程序而言,这就是一次正常的调用系统API,根本不会察觉到有什么不同。当然,如果不是为了通用性,其实并不用Hook CreateFile这些API,因为这个程序所有的打开文件操作都要经过这些API,我们没必要从那没多次调用里一个个字符串去对比,更一般的,我们会直接Hook游戏内部打开文件的相关函数,操作相关内存分配函数,和结构来达到替换文件的目的。

VFS 现成软件

那么有没有通用的VFS呢?显然是有的,比如:MoleBox,Enigma Virtual Box,Enigma,VMP内置的文件系统,Themida的XBundler。其中前三种比较流行,最流行的是Enigma Virtual Box,我们简称为EVB,其实EVB是Enigma这个加壳软件的一个子程序,独立出来的免费版。Enigma和VMP,Themida都是加壳软件,VFS只不过其自带的一个功能而已。只有 MoleBox和 EVB 才是独立的VFS软件。这些软件的作用就是修改EXE,并Hook系统API,从而达到文件操作重新定向到其自己内部的一个VFS上,说白了,就是他们自己会实现一个封包,里面也有文件名大小等信息,打开文件的时候就在封包里找有没对应的文件,有的话就替换,没有的话就让调用正常跑到系统里去。

这里的话,可以说个点,Enigma加载VFS的地方特别的早,导致很多Enigma的加壳验证软件,还在验证阶段就可以通过调用CreateFile来读取VFS的内部文件了,这也就导致了很多加壳软件的内部数据还没过验证就被提取走了,一些采用Enigma来验证的收费汉化组,就存在这类的问题,被大量破解,当然如果一个程序在某个机器上跑起来了,那么VFS其实是可以被随意提取的,因为VFS的本质就是给程序来读写文件的,只不过是有没特殊校验之类的问题。

VFS 文件提取

了解完了上述的知识点,我们就可以知道,当VFS挂到EXE上的时候,我们其实并不需要在意它,当我们成功打开文件,该怎么操作,就怎么操作。

而我们读取它的方式也是如此简单,只需要知道文件名就行了,就像是这个文件存在磁盘上一样操作。

获取文件名

所以说要提取一个VFS里的文件,只需要一个文件就行了,我们如何获取这个文件名呢?

其实有三种方法,

第一种是调用系统的API进去文件名的遍历,如果这个虚拟文件系统有Hook相关的API,我们就可以从中获得文件名,在遍历文件的时候,最好先把原目录下的文件尽量移除,这样我们才能断定出哪些文件是在VFS里的。还有种VFS会有特殊的保护,即大多数软件并没有遍历文件的需求,或者说,即使遍历了,也不影响虚拟文件系统,也就是说,你从文件遍历的API上没办法获得VFS里的文件名。但是你直接用对应的文件打开又可以正常被虚拟文件系统拦截到,这里我猜测是有两种实现,第一种可能是其内部存储文件名用了hash的方式,即你输入正确的文件名,然后计算hash然后与内部的hash进行对比,这样就避免了存储文件名,还有种可能就是单纯没Hook遍历文件的API罢了。当然这个是Hook系统API的情况,如果是Hook游戏引擎内部读取文件的接口,也是一样的道理,也可以有这几种实现方法。

第二种是可以Hook VFS的接口,记录所有的文件名,这样可以在后续移除目录下的文件,并调用接口传入文件名来判断那些文件是在VFS中的。

第三种,也是比较难的一种,这些需要解析VFS的内部实现,来获取文件名,一般可以在接口处跟踪对传入文件名的处理,比较简单的会调用strcmp这类的字符串对比函数进行查找内部释放有该文件,当然这种效率就很差了,不过好处是逆向方便,还有的会计算hash,内部只保存hash,又或者计算hash但是内部也有真实文件名,这块就需要具体问题具体分析了。

不过总的来说,通过Hook 接口,并记录文件名的方法一定程度上都可以获取到VFS内部的文件名情况,虽然可能并不能全部提取完整。

提取文件

得到文件名后就可以开始提取文件了,因为VFS本来就是为程序读取文件服务的,我们当然可以假装自己是程序本身,来调用VFS的接口,传入文件的信息来读取文件。

最简单的肯定是Hook 系统API的这类VFS,因为这类VFS的操作肯定是按照系统API的操作来设计的,我们只需要正常在程序里调用相关的API就可以提取成功,当然如果有些Hook的位置比较偏门,就需要自己找到相关的地址来操作,像是有些会Hook到Call API的地方,而不是API函数的函数头上,比如fopen内部会调用CreateFile,那么其实只需要Hook fopen调用CreateFile的那个Call就好了,并不需要Hook CreateFile的函数主体。

如果是Hook在了内部读取文件的接口上,又或者这个VFS就是游戏封包程序本身,那么这个时候就需要一定的能力来找到对应的接口了,还有接口的参数也一般不会是那些正常文件操作函数的样子,需要仔细去分析出来,可能涉及到内部的数据结构,像是会有个封包对象这样的。当然有些虽然是内部但是就和前面提到的fopen那个一样,只是在调用系统API的前面一点而已,本质上还是Hook 系统的API。

总结还是那句话,游戏怎么读,你就怎么读。

标签:文件,虚拟,文件名,CreateFile,文件系统,Hook,API,思路,VFS
From: https://www.cnblogs.com/Dir-A/p/17737088.html

相关文章

  • 解决 虚拟机VMWARE AUTHORIZATION SERVICE未能启动
    打开控制面板–>点击应用–>在搜索框中输入:vmware搜索–>点击修改或卸载–>进行修复(备注:如果你还有安装包的话也可以打开安装包进行修复)转载:https://www.cnblogs.com/javaxubo/p/16909225.html......
  • esxi上使用esxcli命令设置:虚拟交换机、端口组、vlan、物理接口
    创建虚拟交换机esxclinetworkvswitchstandardadd--vswitch-name=vSwitch2创建端口组esxclinetworkvswitchstandardportgroupadd--portgroup-name=VLAN3999--vswitch-name=vSwitch2设置端口组vlan号esxclinetworkvswitchstandardportgroupset-pVLAN3999--vlan-......
  • 15 | Docker安全:在虚拟的环境中,就不用考虑安全了吗?
    Docker服务:Docker所提供的功能以及在宿主机Linux中的Docker进程Docker镜像:通过Dockerfile构建出来的Docker镜像Docker容器:实际运行的Docker容器,通常来说,一个Docker镜像会生成多个Docker容器。Docker容器运行与Docker服务至上Docker服务安全Docker服务本身需要关注的安全性就......
  • Linux脚本实现文件系统使用率大于90%报警
    Linux脚本实现文件系统使用率大于90%报警disk_used.sh#!/bin/bash#Author:ztj#Date:2023/5/6#Description:磁盘使用率大于90%报警NULLFILE=65disk_filesystem=/tmp/filesystem.txt>$disk_filesystemdf-h|grep-v"Filesystem"|grep-E"^/dev">$disk_filesys......
  • VMware 安装windows server 2008 R2 虚拟机 及VMtools安装
    VMware安装windowsserver2008R2虚拟机并安装VMtools1.新建虚拟机按照图示步骤操作新建虚拟机2.选择安装系统这里我选择安装企业版选择自定义等待安装首次登陆需要修改密码3.给系统安装VMtools默认下一步即可若安装VMtools按钮为灰色或遇到......
  • JDK 21新特性---虚拟线程
    虚拟线程是什么虚拟线程是与原来的平台线程类似的线程,它也是Java.Lang.Thread的一个实例,但它是由Jvm进行管理和调度的。与虚拟内存的实现方式类似,在Jvm中会存在一个Map来维护虚拟线程与实际系统线程的对应关系。当虚拟线程运行时,Jvm会把它分配到一个平台线程上,这个平台线程被......
  • vmare平台上esxi主机,搭建虚拟机ping不通网关
    环境描述:虚拟化平台:vmare5.5物理机系统:esxi虚拟机:centos7.5交换机2台:锐捷和华为机柜位置–》上面的交换机是华为的26机柜1台物理机ip10.1.1.1机柜位置–》上面的交换机是锐捷的12机柜3台物理机IP10.1.1.210.1.1.310.1.1.4物理机插了2个网线,a网线是物理机-管理网10.1.1.......
  • 自己centos7系统制作iso镜像,并新建虚拟机
    一、自己centos7系统制作iso镜像1.前置工作将系统安全配置SELINUX改为disabled,否则制作好的镜像无法登陆!!!vim/etc/selinux/config#将其从enforcing改为disabledSELINUX=disabled2.安装mondorescuecd/etc/yum.repos.dwgetftp://ftp.mondorescue.org/centos/7/x86_64......
  • weblogic乱码报错解决思路
    目录1.集群备份weblogic虚拟机快照备份2.查看主节点控制台面板状态3.尝试启动程序失败4.查看162.主节点日志5.发现程序中乱码6.修改乱码名称7.尝试启动,新的报错还是显示乱码8.修改乱码9.点击更新程序,继续报错9.1依然是乱码10.更新,程序部署路径,换新程序,11.删除程序,处理程序中的乱码......
  • 虚拟机安装VMwares Tools
    目录一.windows系统二.Linux系统一.windows系统1.Windows操作系统安装完成后,VMwarevSphere控制台会提示“该虚拟机上未安装VMwareTools”2.鼠标右击虚拟机,选择客户机操作系统,选择安装VMwareTools3.显示安装VMwareTools对话框,选择挂载,虚拟机的CD/DVD驱动器上会挂VMwareTools......