通过DeviceIoControl读磁盘的方式读取独占文件内容

前言

  windows操作系统中常见的一个文件存储系统是NTFS。在这个文件系统中MFT是它的核心。

  

                                   图一

  MFT是一个数据结构,上图是它的结构,它主要用来存放每个文件和目录在磁盘中的索引。MFT由一个个MFT项构成。每个MFT项的大小是1024Bytes。每个MFT项由固定头结构和多个属性构成。属性用来记录与文件相关的信息。每个属性又由属性头和属性体构成。属性头包含了一些该属性的重要信息,如属性类型、属性大小、是否为常驻属性等等。[1]文件内容也被NTFS看作属性。

   其中,属性分为常驻属性和非常驻属性。如果一个属性的属性体都可以存储在MFT项中,称该属性是常驻属性。如果1024Bytes的MFT项无法记录该属性的所有属性体内容,称该属性是非常驻属性。

   非常驻属性的属性体里存储的不是文件真正的内容,而是文件内容的起始簇号和簇号个数(文件内容存放在MFT外部)。前者方便找到文件在磁盘的位置,后者方便确定文件的大小。这两个信息称为簇流信息。有时候一个文件的内容被存储在多个簇流中,系统就用多个簇流信息来表示文件各部分内容的位置及大小。其中,除了文件的第一个簇流信息的起始簇号代表真正簇号,后面的每个簇流信息中的起始簇号记录的值是前一个起始簇号的相对位置。[2]

   如果我们需要读磁盘上的某个文件,就可以这样操作——从MFT项里获取文件各部分内容的簇流信息,从中获得每个簇流的起始簇号,再用读磁盘的方式打开一个读磁盘句柄,将文件指针依次定位到各簇流的起始簇号位置,依次读出每个簇流内容,最后汇总起来就是该文件的所有内容啦。带着这个方案,使用DeviceIOControl()函数,我们就可以获得MFT项里的簇流信息。这个函数重点需要两个结构体,它们定义在<WinIoCtl.h>头文件里:

typedef struct {
  LARGE_INTEGER StartingVcn;
} STARTING_VCN_INPUT_BUFFER, *PSTARTING_VCN_INPUT_BUFFER;

typedef struct RETRIEVAL_POINTERS_BUFFER {
  DWORD ExtentCount;
  LARGE_INTEGER StartingVcn;
   struct {
    LARGE_INTEGER NextVcn;
    LARGE_INTEGER Lcn;
  } Extents[1];
} RETRIEVAL_POINTERS_BUFFER, *PRETRIEVAL_POINTERS_BUFFER; 

  其中,LCN是逻辑簇号,即这个簇在磁盘中的序号;VCN是虚拟簇号,即这个簇在所属文件所有簇中的序号。第一个结构体中的StartingVcn是文件的起始VCN号,一般值为0。第二个结构体中的ExtentCount表示簇流的数量;StartingVcn表示第一个簇流的起始VCN簇号,一般为0;Extent结构体存放两个与簇流相关的信息,其中NextVcn表示下一个簇流的起始Vcn簇号;Lcn表示该簇流的逻辑簇号。有多少簇流我们就给第二个结构体分配多少个Extent结构体,虽然第二个结构体在定义时只有一个Extent结构体,但是未来给第二个结构体分配堆空间时,可以按照簇流数量来分配Extent结构体。

                        图二

  假设一个文件有三个簇流,则如上图所示。某个簇流的NextVcn减去上一个簇流的NextVcn就可以得到该簇流有多少个簇,每个簇大小可以用GetDiskFreeSpace()函数计算出来,并且一个簇流中的簇的Lcn是顺序累加的。这样我们从该簇流的起始Lcn开始,循环累加Lcn,每次读出簇大小的内容,直到读完该簇流。一个簇流的内容就获得了。然后根据下一个簇流的Lcn,执行相同的操作。直到把整个文件内容读完。

代码

  

typedef unsigned long ULONG;
typedef unsigned long long ULONGLONG;
void ReadDisk(char *pFilePath, char *pDrive, char *pDriveFile)
{
    //--start -get bytes in one cluster [gbioc]
    ULONG sectorsPerCluster, bytesPerSectors, ClusterSize;
    GetDiskFreeSpace(pDrive, &sectorsPerCluster, &bytesPerSectors, NULL, NULL);
    ClusterSize = sectorsPerCluster * bytesPerSectors;
    //--end [gbioc]

    HANDLE hFile = CreateFile(pFilePath, FILE_READ_ATTRIBUTES, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);

    //--start -get file size [gfz]
    ULONGLONG fileSize;
    BY_HANDLE_FILE_INFORMATION fileInfo;
    GetFileInformationByHandle(hFile, &fileInfo);
    fileSize = fileInfo.nFileSizeLow + ((ULONGLONG)fileInfo.nFileSizeHigh << 32);
    //--end [gfz]

    ULONG clusterCount = (fileSize + ClusterSize - 1) / ClusterSize;

    //--start -get file‘s cluster info in disk [gfciid]
    STARTING_VCN_INPUT_BUFFER vcnBuf;
    PRETRIEVAL_POINTERS_BUFFER pOutBuf;
    ULONG outBufSize, readBytes;
    outBufSize = (ULONG)&(((PRETRIEVAL_POINTERS_BUFFER)0)->Extents) + clusterCount * sizeof(pOutBuf -> Extents); //consider that each cluster is in different extent
    pOutBuf = (PRETRIEVAL_POINTERS_BUFFER)malloc(outBufSize * sizeof(char));
    memset(pOutBuf, 0x0, 1);

    vcnBuf.StartingVcn.QuadPart = 0;
    DeviceIoControl(hFile, FSCTL_GET_RETRIEVAL_POINTERS, &vcnBuf, sizeof(vcnBuf), pOutBuf, outBufSize*sizeof(char), &readBytes, NULL); //①.
    //--end [gfciid]

    ULONG extentCount = pOutBuf -> ExtentCount;

    //--start -put each cluster of file into array [pecofia]
    ULONG *Clusters  = (ULONG *)malloc(sizeof(ULONG) * clusterCount);
    LARGE_INTEGER curLcn;
    LARGE_INTEGER preVcn = pOutBuf -> StartingVcn;
    ULONG extentIndx, clusterIndx, clustersInOneExtent;
    for (extentIndx=0,clusterIndx=0; extentIndx<extentCount; ++extentIndx,++clusterIndx){
        curLcn = pOutBuf -> Extents[extentIndx].Lcn;
        for (clustersInOneExtent = (ULONG)(pOutBuf -> Extents[extentIndx].NextVcn.QuadPart - preVcn.QuadPart); clustersInOneExtent; ++clusterIndx, --clustersInOneExtent, ++curLcn.QuadPart){
            Clusters[clusterIndx] = curLcn.QuadPart;
        }
        preVcn = pOutBuf -> Extents[extentIndx].NextVcn;
    }

    free(pOutBuf);
    CloseHandle(hFile);
    //--end [pecofia]

    //--start -read drive like file
    HANDLE hDrive;
    hDrive = CreateFile(pDriveFile, GENERIC_READ, FILE_SHARE_WRITE | FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
    LARGE_INTEGER offset;
    char *pBuf = (char *)malloc(sizeof(char) * ClusterSize);
    for (clusterIndx=0; clusterIndx<clusterCount; ++clusterIndx){
        offset.QuadPart = ClusterSize * Clusters[extentIndx];
        SetFilePointer(hDrive, offset.LowPart, &offset.HighPart, FILE_BEGIN);
        ReadFile(hDrive, pBuf, ClusterSize * sizeof(char), &readBytes, NULL);
    }

    free(pBuf);
    free(Clusters);
    CloseHandle(hDrive);
    //--end
}

int main()
{
  ReadDisk("C:\\file.dat", "C:", "\\\\.\\C:");  

  return 0;
}

以上代码因为专注于功能所以没有加入错误控制,但是实际应用中我们应该对每个Win API、堆分配进行错误控制。

①:关于‘FSCTL_GET_RETRIEVAL_POINTERS’标志位,以下是MSDN上的解释:

The FSCTL_GET_RETRIEVAL_POINTERS operation retrieves a variably sized data structure that describes the allocation and location on disk of a specific file. The structure describes the mapping between virtual cluster numbers (VCN offsets within the file or stream space) and logical cluster numbers (LCN offsets within the volume space).

使用这个标志位可以获得指定文件的内容信息,即使待读文件是独占文件,依然可以用这个标识位获取文件的簇流信息,然后通过读磁盘的方式读出文件内容。

注:如果独占文件,DeviceIoControl的这个标志位使用GENEIC_READ的话,会返回错误句柄。

结尾

欢迎在评论区留言,说出你的见解,大家一起讨论~

参考

[1]. http://huifox.com/2018/01/25/ntfs-%E5%B8%B8%E9%A9%BB%E5%B1%9E%E6%80%A7%E4%B8%8E%E9%9D%9E%E5%B8%B8%E9%A9%BB%E5%B1%9E%E6%80%A7/

[2]. http://blog.51cto.com/shujvhuifu/1813848

原文地址:https://www.cnblogs.com/yichi/p/9486697.html

时间: 2024-08-08 02:13:11

通过DeviceIoControl读磁盘的方式读取独占文件内容的相关文章

五种方式让你在java中读取properties文件内容不再是难题

一.背景 最近,在项目开发的过程中,遇到需要在properties文件中定义一些自定义的变量,以供java程序动态的读取,修改变量,不再需要修改代码的问题.就借此机会把Spring+SpringMVC+Mybatis整合开发的项目中通过java程序读取properties文件内容的方式进行了梳理和分析,先和大家共享. 二.项目环境介绍 Spring 4.2.6.RELEASE SpringMvc 4.2.6.RELEASE Mybatis 3.2.8 Maven 3.3.9 Jdk 1.7 Id

读取Pdm文件内容

Pdm文件,就是PowerDesigner软件生成的文件,用来设计数据库表结构非常适合.其实,它的文件存储格式就是Xml,网上有很多代码,可以读取pdm文件内容.代码可以使用,但一般只能读取简单的pdm文件,如下图所示 好了,抛砖已完成.下面要引玉了,如果要读取下图这个pdm文件内容,网上的若干代码就不行了 最后,说下重点,已经我的加工整理,下面提供代码里可以读取上述这种复制格式的pdm文件.调用也非常简单.如下所示 string pdmFileFullName = @"E:\pdmTest.p

深入学习python解析并读取PDF文件内容的方法

这篇文章主要学习了python解析并读取PDF文件内容的方法,包括对学习库的应用,python2.7和python3.6中python解析PDF文件内容库的更新,包括对pdfminer库的详细解释和应用.主要参考了一些已有的博客内容,代码. 主要思路是首先利用一个做项目的形式,描述所做的问题,运行环境,和需要安装的库,然后写代码,此代码是在python2.7中运行,然后写出在python3.6中运行的代码,并详细解释python2.7和python3.6中python库的一些不同之处,最后详细的

.NET读取Office文件内容(word、excel、ppt)

引用命名空间 1 using Microsoft.Office.Core; 2 using Word = Microsoft.Office.Interop.Word; 3 using Excel = Microsoft.Office.Interop.Excel; 4 using PowerPoint = Microsoft.Office.Interop.PowerPoint; Word文件的读取 1 public string ReadFile() 2 { 3 string text = str

PHP读取Excel文件内容

PHP读取Excel文件内容 项目需要读取Excel的内容,从百度搜索了下,主要有两个选择,第一个是PHPExcelReader,另外一个是PHPExcel. PHPExcelReader比较轻量级,仅支持Excel的读取,实际上就是一个Reader.但是可惜的是不能够支持Excel 2007的格式(.xlsx). PHPExcel比较强大,能够将内存中的数据输出成Excel文件,同时还能够对Excel做各种操作,下面主要介绍下如何使用PHPExcel进行Excel 2007格式(.xlsx)文

Java编程:使用Java读取Excel文件内容

微软的ODBC驱动程序把工作表中的第一行作为列名(译者注:即字段名),工作表名作为数据库表名. 要通过JDBC访问工作表,我们还必须创建一个新的ODBC数据源,在Windows 2000系统上创建数据源的过程如下: 进入“控制面板” --> “管理工具” --> “数据源(ODBC)”,(译者注:打开后选择系统DSN),点击添加,在弹出窗口中选择“Driver do Microsoft Excel(*.xls)” 然后在数据源名处输入一个名字myexcel(译者注:相当于数据库名),然后点击“

读取XML文件内容

myeclipse中类的格式 上面中的RunMain.java为程序执行的入口,JdbcUtil.java为实体类,XmlDocumentUtil.java执行解释xml文件与获取里面的属性,程序所需要导入的包为dom4j-1.6.1.jar与jaxen-1.1-beta-6.jar,jdbc.cfg.xml文件为需解释的xml文件此程序下载 jdbc.cfg.xml <?xml version="1.0" encoding="UTF-8"?> <

按格式读取csv文件内容

string path = @"C:\Users\keen_\Downloads\upload\upload\Upload\20140701141934_export.csv"; ImportDataTable(path); //2014-07-01 //get csv file to datatable private static DataTable ImportDataTable(string filepath) { DataTable mydt = new DataTable(

使用property读取外部文件内容

package com.jdbc;import java.io.File;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;import java.io.InputStream;import java.util.Properties;/** * 使用property读取外部文件内容 * @author Administrator * */public cla